阿姆斯壯數

December 2, 2021

在 n 位的整數中,若加總每個數字的 n 次方後等於該整數,該整數稱為阿姆斯壯數(Armstrong number),又稱自戀數(Narcissistic number)(因為各數字 n 次方後加總又等於本身,感覺很自戀?)。

例如 153 可以滿足 1³ + 5³ + 3³ = 153,153 就是個阿姆斯壯數,阿姆斯壯數有 88 個,最大為 39 位數的 115132219018763992565095597973971522401,已證實超過 39 位數不存在阿姆斯壯數。

解法思路

尋找阿姆斯壯數,就是在問如何將數字分解為個位數、十位數、百位數……,這只要使用除法與餘數運算就可以了,例如要找出所有三位數阿姆斯壯數。輸入 input 為 n 位數,可如下分解出位數:

個 位 (input / 100) % 10

十位 (input / 101) % 10

百 位 (input / 102) % 10

n 位 (input / 10n-1) % 10

底下列出的實作僅逐一列舉數字並判斷是否為阿姆斯壯數,位數大時求解時間會急劇拉長。可改進的方法之一:

  • 對於同為 n 位數而言,數字的 n 次方重複運算是不必要的,這部份可先製表而後直接查表並進行加總。
  • 進行數字裁剪,將同為 n 位數的數劃分為數個集合,將尋找阿姆斯壯數縮剪為尋找包括阿姆斯壯數的集合。例如 3 位數中,122、212、221 會屬於同一集合(因此需要搭配快速排列組合),詢問此集合是否包括 1³ + 2³ + 2³ = 17,答案為否(因而需要搭配有序集合以快速確認是否包括),而 135、315、153 屬於同一集合,詢問此集合是否包括 1³ + 3³ + 5³ = 153,答案為是,153 為阿姆斯壯數,在位數多時,可藉由集合裁剪掉大量單獨數字加總後測試的需求,從而加快求解速度。

程式實作:

#include <stdio.h> 
#include <stdlib.h> 

int isNarcissistic(double);
void narcissistic(double*, int);

int main(void) { 
    double armstrongs[88] = {0};
    narcissistic(armstrongs, 3);
    int i;
    for(i = 0; armstrongs[i] != 0; i++) {
        printf("%.0f\n", armstrongs[i]);
    }
    return 0; 
} 

int isNarcissistic(double number) {
    int digits[39] = {0};
    double num = number;
    int i;
    for(i = 0; num != 0; i++) {
        digits[i] = (int) num % 10;
        num = (int) (num / 10);
    }
    double sum = 0.0;
    int j;
    for(j = 0; j <= i; j++) {
        sum += pow(digits[j], i);
    }
    return sum == number;
}

void narcissistic(double* armstrongs , int n) {
    double bound = pow(10, n < 40 ? n : 39);
    double i;
    int j;
    for(i = 0, j = 0; i < bound; i++) if(isNarcissistic(i)) {
        armstrongs[j] = i; j++;
    }
}
import java.math.BigInteger;
import java.util.*;
import static java.lang.Math.*;

public class Armstrong {
    private static double[][] pows;
    
    public static List<Double> narcissistic(int n) {
        pows = new double[n + 1][];
        pows[1] = new double[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
        for(int i = 2; i <= n; i++) {
            pows[i] = new double[] {
                0, 1, pows[i - 1][2] * 2, pows[i - 1][3] * 3, 
                pows[i - 1][4] * 4, pows[i - 1][5] * 5, 
                pows[i - 1][6] * 6, pows[i - 1][7] * 7, 
                pows[i - 1][8] * 8, pows[i - 1][9] * 9
            };
        }
    
        List<Double> armstrongs = new ArrayList<>();
        double bound = pow(10, n < 40 ? n : 39);
        for(double i = 1; i < bound; i++) if(isNarcissistic(i)) {
            armstrongs.add(i);
        }
        return armstrongs;
    }
    
    public static boolean isNarcissistic(double number) {
        List<Integer> digits = new ArrayList<>();
        double num = number;
        while(num != 0) {
            digits.add((int) num  % 10);
            num = floor(num / 10);
        }
        double sum = 0;
        for(Integer d : digits) {
            sum += pows[digits.size()][d];
        }
        return sum == number;
    }
    

    public static void main(String[] args) {
        for(Double d : narcissistic(7)) {
            System.out.printf("%.0f%n", d);
        }
    }
}
from functools import reduce

def toDigits(num):
    return [] if num == 0 else ([num % 10] + toDigits(num // 10))

def isNarcissistic(number):
    digits = toDigits(number)
    return reduce(lambda sum, d: sum + d ** len(digits), 
               digits, 0) == number

def narcissistic(n):
    return [i for i in range(1, 10 ** (n if n < 40 else 39)) 
                  if isNarcissistic(i)]

print(narcissistic(3))
import scala.math.BigInt
import scala.math.pow

def toDigits(num: BigInt): List[Int] = {
    if(num == 0) Nil else (num % 10).toInt :: toDigits(num / 10)
}

def isNarcissistic(number: BigInt) = {
    val digits = toDigits(number)
    (0 /: digits) { (sum, d) => sum + pow(d, digits.size).toInt } == number
}

def narcissistic(n: Int) = {
    for(i <- BigInt(1) until BigInt(10).pow(if(n < 40) n else 39)
        if isNarcissistic(i)) yield i
}

narcissistic(3).foreach(println _)
def toDigits(num)
    num == 0 ? [] : ([num % 10]) + toDigits(num / 10)
end

def isNarcissistic(number)
    digits = toDigits(number)
    digits.reduce(0) {|sum, d| sum + d ** digits.size} == number
end

def narcissistic(n)
    (1...10 ** (n < 40 ? n : 39)).select {|i| isNarcissistic(i)}
end

print(narcissistic(3))
function isNarcissistic(number) {
    var digits = [];
    var num = number;
    while(num != 0) {
        digits.push(num  % 10);
        num = parseInt(num / 10);
    }
    var sum = 0;
    for(var i = 0; i < digits.length; i++) {
        sum += Math.pow(digits[i], digits.length);
    }
    return sum == number;
}
    
function narcissistic(n) {
    var armstrongs = [];
    var bound = Math.pow(10, n < 40 ? n : 39);
    for(var i = 1; i < bound; i++) if(isNarcissistic(i)) {
        armstrongs.push(i);
    }
    return armstrongs;
}

print(narcissistic(3));
toDigits num =
    if num == 0 then [] else (num `mod` 10) : toDigits (num `div` 10)

isNarcissistic number = 
    number == (foldl (\sum d -> sum + d ^ (length digits)) 0 digits)
    where digits = toDigits number

narcissistic n =
    [i | i <- [1..10 ^ (if n < 40 then n else 39)], isNarcissistic(i)]

main = print \$ narcissistic 3