M.Hiroi's Home Page

JavaScript Programming

JavaScript Junk Scripts


Copyright (C) 2025 Makoto Hiroi
All rights reserved.

順列と組み合わせ

●仕様 (ジェネレータ版)

●プログラムリスト

//
// combination.js : 順列と組み合わせ
//
// Copyright (c) 2025 Makoto Hiroi
//
// Released under the MIT license
// https://opensource.org/license/mit/
//

// 数列の生成
function iota(s, n, step = 1) {
    let buff = new Array(n);
    for (let i = 0; i < n; i++) {
        buff[i] = s;
        s += step;
    }
    return buff;
}

// ジェネレータの要素を配列に格納して返す
function bag_of(gen) {
    let bag = [];
    for (let x of gen) bag.push(x);
    return bag;
}

// 階乗
function factorial(n) {
    let a = 1n;
    while (n > 0) a *= BigInt(n--);
    return a;
}

// n 個の中から r 個を選ぶ順列
function permutation_number(n, r) {
    let a = 1n;
    while (r-- > 0) a *= BigInt(n--);
    return a;
}

// 順列の生成
function* permutation(n, xs, ps = [], flag = new Set()) {
    if (n == 0) {
        yield [...ps];
    } else {
        for (let x of xs) {
            if (!flag.has(x)) {
                flag.add(x);
                ps.push(x);
                yield* permutation(n - 1, xs, ps, flag);
                ps.pop();
                flag.delete(x);
            }
        }
    }
}

// 重複順列
function* permutation_with_repetition(n, xs, ps = []) {
    if (n == 0) {
        yield [...ps];
    } else {
        for (let x of xs) {
            ps.push(x);
            yield* permutation_with_repetition(n - 1, xs, ps);
            ps.pop();
        }
    }
}

// 組み合わせの数
function combination_number(n, r) {
    return permutation_number(n, r) / factorial(r);
}

// 組み合わせの生成
function* combination(n, xs, ys = [], i = 0) {
    if (n == ys.length) {
        yield [...ys];
    } else if (xs.length - i >= n - ys.length) {
        ys.push(xs[i]);
        yield* combination(n, xs, ys, i + 1);
        ys.pop();
        yield* combination(n, xs, ys, i + 1);
    }
}

// 重複組み合わせ
function* combination_with_repetition(n, xs, ys = [], i = 0) {
    if (n == ys.length) {
        yield [...ys];
    } else if (xs.length > i) {
        ys.push(xs[i]);
        yield* combination_with_repetition(n, xs, ys, i);
        ys.pop();
        yield* combination_with_repetition(n, xs, ys, i + 1);
    }
}

// 完全順列
// (0 .. m-1) の中から m 個を選ぶ
function* derangement(m, ps = [], flag = new Set()) {
    if (ps.length == m) {
        yield [...ps];
    } else {
        for (let x = 0; x < m; x++) {
            if (x != ps.length && !flag.has(x)) {
                flag.add(x);
                ps.push(x);
                yield* derangement(m, ps, flag);
                ps.pop();
                flag.delete(x);
            }
        }
    }
}

// モンモール数
function montmort_number(n) {
    let a = 0n, b = 1n;
    for (let i = 1; i < n; i++) [a, b] = [b, BigInt(i + 1) * (a + b)]
    return a;
}

// 直積集合
function* product_sub(xs, i = 0, a = []) {
    if (i == xs.length) {
        yield [...a];
    } else {
        for (let x of xs[i]) {
            a.push(x);
            yield* product_sub(xs, i + 1, a);
            a.pop();
        }
    }
}

function* product(...args) {
    yield* product_sub(args);
}

export {
    iota, bag_of, factorial, permutation_number, permutation,
    combination_number, combination, derangement, montmort_number,
    product, permutation_with_repetition, combination_with_repetition
}

●簡単なテスト

//
// test_combination.js : 順列と組み合わせのテスト
//
// Copyright (c) 2025 Makoto Hiroi
//
// Released under the MIT license
// https://opensource.org/license/mit/
//
import * as M from './combination.js';
console.log("---- iota ----");
console.log(M.iota(0, 4));           // [0,1,2,3]
console.log(M.iota(0, 4, 2));        // [0,2,4,6]
console.log(M.iota(10n, 4, 1n));     // [10n, 11n, 12n, 13n,]
console.log(M.iota(10n, 4, -1n));    // [10n, 9n, 8n, 7n,]

console.log("---- factorial ----");
console.log(M.factorial(0));   // 1n
console.log(M.factorial(9));   // 362880n
console.log(M.factorial(10));  // 3628800n
console.log(M.factorial(15));  // 1307674368000n

console.log("---- permutation_number ----");
console.log(M.permutation_number(6, 1))   // 6
console.log(M.permutation_number(6, 3))   // 120
console.log(M.permutation_number(6, 6))   // 720
console.log(M.permutation_number(10, 4))  // 5040

console.log("---- permutation ----");
console.log(M.bag_of(M.permutation(2, [1,2,3])));
console.log(M.bag_of(M.permutation(3, [1,2,3])));
console.log(M.bag_of(M.permutation_with_repetition(2, [1,2])));
console.log(M.bag_of(M.permutation_with_repetition(2, [1,2,3])));

console.log("---- combination_number ----");
console.log(M.combination_number(5, 5))     // 1
console.log(M.combination_number(5, 3))     // 10
console.log(M.combination_number(5, 1))     // 5
console.log(M.combination_number(60, 30))   // 118264581564861424n

console.log("---- combination ----");
console.log(M.bag_of(M.combination(1, [1,2,3,4,5])));
console.log(M.bag_of(M.combination(3, [1,2,3,4,5])));
console.log(M.bag_of(M.combination(5, [1,2,3,4,5])));
console.log(M.bag_of(M.combination_with_repetition(2, [1,2])));
console.log(M.bag_of(M.combination_with_repetition(2, [1,2,3])));

console.log("---- montmort_number ----");
console.log(M.montmort_number(3))     // 2
console.log(M.montmort_number(4))     // 9
console.log(M.montmort_number(5))     // 44
console.log(M.montmort_number(20))    // 895014631192902121n

console.log("---- derangement ----");
console.log(M.bag_of(M.derangement(2)));
console.log(M.bag_of(M.derangement(3)));
console.log(M.bag_of(M.derangement(4)));

console.log("---- product ----");
console.log(M.bag_of(M.product([1,2,3,4])));
console.log(M.bag_of(M.product([1,2],[3,4])));
console.log(M.bag_of(M.product([1,2],[3,4,5])));
console.log(M.bag_of(M.product([1,2],[3,4],[5,6])));

●実行結果

$ node test_combination.js
---- iota ----
[ 0, 1, 2, 3 ]
[ 0, 2, 4, 6 ]
[ 10n, 11n, 12n, 13n ]
[ 10n, 9n, 8n, 7n ]
---- factorial ----
1n
362880n
3628800n
1307674368000n
---- permutation_number ----
6n
120n
720n
5040n
---- permutation ----
[ [ 1, 2 ], [ 1, 3 ], [ 2, 1 ], [ 2, 3 ], [ 3, 1 ], [ 3, 2 ] ]
[
  [ 1, 2, 3 ],
  [ 1, 3, 2 ],
  [ 2, 1, 3 ],
  [ 2, 3, 1 ],
  [ 3, 1, 2 ],
  [ 3, 2, 1 ]
]
[ [ 1, 1 ], [ 1, 2 ], [ 2, 1 ], [ 2, 2 ] ]
[
  [ 1, 1 ], [ 1, 2 ],
  [ 1, 3 ], [ 2, 1 ],
  [ 2, 2 ], [ 2, 3 ],
  [ 3, 1 ], [ 3, 2 ],
  [ 3, 3 ]
]
---- combination_number ----
1n
10n
5n
118264581564861424n
---- combination ----
[ [ 1 ], [ 2 ], [ 3 ], [ 4 ], [ 5 ] ]
[
  [ 1, 2, 3 ], [ 1, 2, 4 ],
  [ 1, 2, 5 ], [ 1, 3, 4 ],
  [ 1, 3, 5 ], [ 1, 4, 5 ],
  [ 2, 3, 4 ], [ 2, 3, 5 ],
  [ 2, 4, 5 ], [ 3, 4, 5 ]
]
[ [ 1, 2, 3, 4, 5 ] ]
[ [ 1, 1 ], [ 1, 2 ], [ 2, 2 ] ]
[ [ 1, 1 ], [ 1, 2 ], [ 1, 3 ], [ 2, 2 ], [ 2, 3 ], [ 3, 3 ] ]
---- montmort_number ----
2n
9n
44n
895014631192902121n
---- derangement ----
[ [ 1, 0 ] ]
[ [ 1, 2, 0 ], [ 2, 0, 1 ] ]
[
  [ 1, 0, 3, 2 ],
  [ 1, 2, 3, 0 ],
  [ 1, 3, 0, 2 ],
  [ 2, 0, 3, 1 ],
  [ 2, 3, 0, 1 ],
  [ 2, 3, 1, 0 ],
  [ 3, 0, 1, 2 ],
  [ 3, 2, 0, 1 ],
  [ 3, 2, 1, 0 ]
]
---- product ----
[ [ 1 ], [ 2 ], [ 3 ], [ 4 ] ]
[ [ 1, 3 ], [ 1, 4 ], [ 2, 3 ], [ 2, 4 ] ]
[ [ 1, 3 ], [ 1, 4 ], [ 1, 5 ], [ 2, 3 ], [ 2, 4 ], [ 2, 5 ] ]
[
  [ 1, 3, 5 ],
  [ 1, 3, 6 ],
  [ 1, 4, 5 ],
  [ 1, 4, 6 ],
  [ 2, 3, 5 ],
  [ 2, 3, 6 ],
  [ 2, 4, 5 ],
  [ 2, 4, 6 ]
]

●高階関数版

//
// comb_func.js : 順列と組み合わせ (高階関数版)
//
// Copyright (c) 2025 Makoto Hiroi
//
// Released under the MIT license
// https://opensource.org/license/mit/
//

// 数列の生成
function iota(s, n, step = 1) {
    let buff = new Array(n);
    for (let i = 0; i < n; i++) {
        buff[i] = s;
        s += step;
    }
    return buff;
}

// 要素を配列に格納して返す
function bag_of(func, ...args) {
    let bag = [];
    func(xs => bag.push(xs), ...args);
    return bag;
}

// 階乗
function factorial(n) {
    let a = 1n;
    while (n > 0) a *= BigInt(n--);
    return a;
}

// n 個の中から r 個を選ぶ順列
function permutation_number(n, r) {
    let a = 1n;
    while (r-- > 0) a *= BigInt(n--);
    return a;
}

// 順列の生成
function permutation(fn, n, xs, ps = [], flag = new Set()) {
    if (n == 0) {
        fn([...ps]);
    } else {
        for (let x of xs) {
            if (!flag.has(x)) {
                flag.add(x);
                ps.push(x);
                permutation(fn, n - 1, xs, ps, flag);
                ps.pop();
                flag.delete(x);
            }
        }
    }
}

// 重複順列
function permutation_with_repetition(fn, n, xs, ps = []) {
    if (n == 0) {
        fn([...ps]);
    } else {
        for (let x of xs) {
            ps.push(x);
            permutation_with_repetition(fn, n - 1, xs, ps);
            ps.pop();
        }
    }
}

// 組み合わせの数
function combination_number(n, r) {
    return permutation_number(n, r) / factorial(r);
}

// 組み合わせの生成
function combination(fn, n, xs, ys = [], i = 0) {
    if (n == ys.length) {
        fn([...ys]);
    } else if (xs.length - i >= n - ys.length) {
        ys.push(xs[i]);
        combination(fn, n, xs, ys, i + 1);
        ys.pop();
        combination(fn, n, xs, ys, i + 1);
    }
}

// 重複組み合わせ
function combination_with_repetition(fn, n, xs, ys = [], i = 0) {
    if (n == ys.length) {
        fn([...ys]);
    } else if (xs.length > i) {
        ys.push(xs[i]);
        combination_with_repetition(fn, n, xs, ys, i);
        ys.pop();
        combination_with_repetition(fn, n, xs, ys, i + 1);
    }
}

// 完全順列
// (0 .. m-1) の中から m 個を選ぶ
function derangement(fn, m, ps = [], flag = new Set()) {
    if (ps.length == m) {
        fn([...ps]);
    } else {
        for (let x = 0; x < m; x++) {
            if (x != ps.length && !flag.has(x)) {
                flag.add(x);
                ps.push(x);
                derangement(fn, m, ps, flag);
                ps.pop();
                flag.delete(x);
            }
        }
    }
}

// モンモール数
function montmort_number(n) {
    let a = 0n, b = 1n;
    for (let i = 1; i < n; i++) [a, b] = [b, BigInt(i + 1) * (a + b)]
    return a;
}

// 直積集合
function product_sub(fn, xs, i = 0, a = []) {
    if (i == xs.length) {
        fn([...a]);
    } else {
        for (let x of xs[i]) {
            a.push(x);
            product_sub(fn, xs, i + 1, a);
            a.pop();
        }
    }
}

function product(fn, ...args) {
    product_sub(fn, args);
}

export {
    iota, bag_of, factorial, permutation_number, permutation,
    combination_number, combination, derangement, montmort_number,
    product, permutation_with_repetition, combination_with_repetition
}
//
// test_comb_func.js : 順列と組み合わせのテスト
//
// Copyright (c) 2025 Makoto Hiroi
//
// Released under the MIT license
// https://opensource.org/license/mit/
//
import * as M from './comb_func.js';
console.log("---- iota ----");
console.log(M.iota(0, 4));           // [0,1,2,3]
console.log(M.iota(0, 4, 2));        // [0,2,4,6]
console.log(M.iota(10n, 4, 1n));     // [10n, 11n, 12n, 13n,]
console.log(M.iota(10n, 4, -1n));    // [10n, 9n, 8n, 7n,]

console.log("---- factorial ----");
console.log(M.factorial(0));   // 1n
console.log(M.factorial(9));   // 362880n
console.log(M.factorial(10));  // 3628800n
console.log(M.factorial(15));  // 1307674368000n

console.log("---- permutation_number ----");
console.log(M.permutation_number(6, 1))   // 6
console.log(M.permutation_number(6, 3))   // 120
console.log(M.permutation_number(6, 6))   // 720
console.log(M.permutation_number(10, 4))  // 5040

console.log("---- permutation ----");
console.log(M.bag_of(M.permutation, 2, [1,2,3]));
console.log(M.bag_of(M.permutation, 3, [1,2,3]));
console.log(M.bag_of(M.permutation_with_repetition, 2, [1,2]));
console.log(M.bag_of(M.permutation_with_repetition, 2, [1,2,3]));

console.log("---- combination_number ----");
console.log(M.combination_number(5, 5))     // 1
console.log(M.combination_number(5, 3))     // 10
console.log(M.combination_number(5, 1))     // 5
console.log(M.combination_number(60, 30))   // 118264581564861424n

console.log("---- combination ----");
console.log(M.bag_of(M.combination, 1, [1,2,3,4,5]));
console.log(M.bag_of(M.combination, 3, [1,2,3,4,5]));
console.log(M.bag_of(M.combination, 5, [1,2,3,4,5]));
console.log(M.bag_of(M.combination_with_repetition, 2, [1,2]));
console.log(M.bag_of(M.combination_with_repetition, 2, [1,2,3]));

console.log("---- montmort_number ----");
console.log(M.montmort_number(3))     // 2
console.log(M.montmort_number(4))     // 9
console.log(M.montmort_number(5))     // 44
console.log(M.montmort_number(20))    // 895014631192902121n

console.log("---- derangement ----");
console.log(M.bag_of(M.derangement, 2));
console.log(M.bag_of(M.derangement, 3));
console.log(M.bag_of(M.derangement, 4));

console.log("---- product ----");
console.log(M.bag_of(M.product, [1,2,3,4]));
console.log(M.bag_of(M.product, [1,2],[3,4]));
console.log(M.bag_of(M.product, [1,2],[3,4,5]));
console.log(M.bag_of(M.product, [1,2],[3,4],[5,6]));

実行結果は同じなので省略します。速度は高階関数版の方が速いようです。

//
// test_comb.js : 簡単な速度比較
//
// Copyright (c) 2025 Makoto Hiroi
//
// Released under the MIT license
// https://opensource.org/license/mit/
//
import * as G from './combination.js';
import * as H from './comb_func.js';

for (let size = 8; size <= 10; size++) {
    console.log(`----- ${size} -----`);
    let c = 0;
    var s = new Date().getTime();
    for (let xs of G.permutation(size, G.iota(0, size))) {
        c++;
    }
    var e = new Date().getTime();
    console.log(c, e - s);
    c = 0;
    s = new Date().getTime();
    H.permutation(x => c++, size, H.iota(0, size));
    var e = new Date().getTime();
    console.log(c, e - s);
}
$ node test_comb.js
----- 8 -----
40320 824
40320 53
----- 9 -----
362880 611
362880 192
----- 10 -----
3628800 6302
3628800 2011

●サンプルプログラム

●パズル「小町分数」

下図の A から I の場所に 1 から 9 までの数字をひとつずつ配置します。3 つの分数を足すと 1 / N になる配置を求めてください。

\( \dfrac{A}{BC} + \dfrac{D}{EF} + \dfrac{G}{HI} = \dfrac{1}{N} \)

ex)
3/27 + 6/54 + 9/81 = 1/3
3/54 + 6/72 + 9/81 = 1/4

図 : 小町分数 (1)

このパズルの元ネタは N = 1 の場合で、参考文献『超々難問数理パズル 解けるものなら解いてごらん』に掲載されています。ちなみに、3 つの分数の和が整数になる場合、その値は 1 しかありません。また、値が 1 / N (N は整数) になる場合は 2, 3, 4, 6, 10 の 5 通りです。

プログラムは次のようになります。

//
// komachi_ratio.js : 小町分数
//
// Copyright (c) 2025 Makoto Hiroi
//
// Released under the MIT license
// https://opensource.org/license/mit/
//
import Ratio from './ratio.js';
import { iota, permutation } from './combination.js';

function check(a, b, c, d, e, f, g, h, i) {
    let x = new Ratio(a, b * 10n + c);
    let y = new Ratio(d, e * 10n + f);
    let z = new Ratio(g, h * 10n + i);
    let r = x.add(y).add(z);
    if (a < d && d < g && r.numerator == 1n) {
        let s = `${a}/${b}${c} + ${d}/${e}${f} + ${g}/${h}${i} = ${r}`;
        console.log('%s', s);
    }
}

function komachi_ratio() {
    for (let xs of permutation(9, iota(1n, 9, 1n))) check(...xs);
}

// 実行
komachi_ratio();

基本的には単純な生成検定法ですが、分子の数字を a < d < g と限定することで、重複解を生成しないように工夫しています。それから、このプログラムでは 3 つの分数の和が 1 になる場合も解を出力します。プログラムを実行するときはご注意くださいませ。

$ node komachi_ratio.js
1/24 + 3/56 + 7/98 = 1/6
1/26 + 5/39 + 7/84 = 1/4
1/32 + 5/96 + 7/84 = 1/6
1/38 + 2/95 + 4/76 = 1/10
1/48 + 5/32 + 7/96 = 1/4
1/56 + 3/72 + 9/84 = 1/6
1/96 + 5/32 + 7/84 = 1/4
1/96 + 5/48 + 7/32 = 1/3
2/18 + 5/63 + 7/49 = 1/3
2/19 + 4/57 + 6/38 = 1/3
3/27 + 6/54 + 9/81 = 1/3
3/48 + 5/16 + 9/72 = 1/2
3/54 + 6/72 + 9/81 = 1/4
5/34 + 7/68 + 9/12 = 1

●ナルシシスト数

n 桁の自然数 m において、各桁の n 乗の和が m に等しい数を「ナルシシスト数 (narcissistic number)」といいます。定義により 1 桁の数 (1 - 9) はナルシシスト数になります。2 桁のナルシシスト数はありませんが、3 桁のナルシシスト数は以下の 4 つがあります。

153 = 13 + 53 + 33 = 1 + 125 + 27 = 153
370 = 33 + 73 + 03 = 27 + 343 + 0 = 370
371 = 33 + 73 + 13 = 27 + 343 + 1 = 371
407 = 43 + 03 + 73 = 64 + 0 + 343 = 407

ナルシシスト数は有限個しかありません。n 桁のナルシシスト数の最小値は 10n-1 で、最大値は n * 9n です。ナルシシスト数が存在するには次の不等式が成立しないといけません。

10n-1 <= n * 9n

両辺を 9n-1 で割ると以下の式になります。

(10 / 9)n-1 <= 9 * n

n が小さいとき上式は成立しますが、n が大きくなると指数関数の値は爆発的に増加するので、上式はいづれ不成立になります。具体的には n が 60 を超えると不成立になります。

n = 60: (10 / 9)59 = 500.8327, 9 * 60 = 540, 成立
n = 61: (10 / 9)60 = 556.4808, 9 * 61 = 549, 不成立

●プログラムの作成

ナルシシスト数を求める場合、たとえば 4 桁のナルシシスト数であれば、1000 - 9999 までの整数値を順番に生成し、各桁の 4 乗和の計算結果が元の整数と等しければ、その数はナルシシスト数と判定することができます。ただし、この方法はとても非効率です。1634 は 4 桁のナルシシスト数ですが、それ以外の数字の並び方 (23 通り) はナルシシスト数にはなりません。

各桁の n 乗和は数字の並びに関係なく計算することができるので、この場合は 0 - 9 から n 個の数字を選ぶ重複組み合わせを求めたほうが効率的です。選んだ数字の組み合わせから整数値を生成し、その値を桁ごとに分解した結果、元の組み合わせと同じであれば、その数をナルシスト数と判定することができます。

数値を桁に分解するプログラムは簡単です。プログラムと実行例を示します。

リスト : 数の分解

// 数を桁ごとに分解
function split_digit(n) {
    let a = [];
    while (n > 0n) {
        a.push(n % 10n);
        n /= 10n;
    }
    return a;
}

// 数字の個数をカウント
function count_number(xs) {
    let a = [0,0,0,0,0, 0,0,0,0,0];
    for (let x of xs) a[x]++;
    return a;
}
> split_digit(1634n)
[ 4n, 3n, 6n, 1n ]
> split_digit(45678n)
[ 8n, 7n, 6n, 5n, 4n ]
> split_digit(123123456n)
[
  6n, 5n, 4n, 3n, 2n,
  1n, 3n, 2n, 1n
]

> count_number(split_digit(1634n))
[
  0, 1, 0, 1, 1,
  0, 1, 0, 0, 0
]
> count_number(split_digit(45678n))
[
  0, 0, 0, 0, 1,
  1, 1, 1, 1, 0
]
> count_number(split_digit(123123456n))
[
  0, 2, 2, 2, 1,
  1, 1, 0, 0, 0
]

累乗和を求める場合、2 つの配列で畳み込みを行う関数 fold_left2 を用意すると簡単です。

リスト : 畳み込み

function fold_left2(fn, a, xs, ys) {
    let k = Math.min(xs.length, ys.length);
    for (let i = 0; i < k; i++) a = fn(a, xs[i], ys[i]);
    return a;
}

引数 xs に count_number の返り値を渡し、ys に累乗 i ** n (0 <= i < 10) を格納した配列を渡します。そして、関数 fn に (a, x, y) => a + x * y を、初期値 a に 0 を渡せば、累乗輪を求めることができます。

最後に n 桁のナルシシスト数を求める関数 solver を作ります。

リスト : n 桁のナルシシスト数を求める

function solver(n) {
    let ps = iota(0n, 10, 1n).map(x => x ** n);
    for (let xs of combination_with_repetition(n, iota(0, 10))) {
        let cs = count_number(xs);
        let x = fold_left2((a, x, y) => a += BigInt(x) * y, 0n, cs, ps);
        let ds = count_number(split_digit(x));
        if (equal_array(cs, ds)) console.log(x);
    }
}

最初に iota と map を使って、数の n 乗を格納した配列 ps を作ります。次に、combination_with_repetition で重複組み合わせを生成し、count_number で数字を数えて変数 cs にセットします。そして、累乗和を fold_left2 で求めて変数 x にセットします。

split_digit で x を桁ごとに分解し、count_number で数字を数え、結果を変数 ds にセットします。cs と ds が等しければ、x はナルシシスト数です。console.log で x を表示します。

それでは実行してみましょう。

$ node narci.js
----- 1 -----
1n
2n
3n
4n
5n
6n
7n
8n
9n
----- 2 -----
----- 3 -----
370n
407n
153n
371n
----- 4 -----
8208n
1634n
9474n
----- 5 -----
93084n
92727n
54748n
----- 6 -----
548834n
----- 7 -----
9800817n
4210818n
1741725n
9926315n
----- 8 -----
24678050n
24678051n
88593477n
----- 9 -----
146511208n
912985153n
472335975n
534494836n
----- 10 -----
4679307774n
----- 11 -----
32164049650n
40028394225n
42678290603n
49388550606n
32164049651n
94204591914n
44708635679n
82693916578n
----- 12 -----
----- 13 -----
----- 14 -----
28116440335967n
----- 15 -----
----- 16 -----
4338281769391370n
4338281769391371n
----- 17 -----
35875699062250035n
21897142587612075n
35641594208964132n
----- 18 -----

18 桁程度であれば JavaScript でもナルシシスト数を簡単に求めることができるようです。これ以上桁数を増やすには、より高速なプログラミング言語を使う、何かしらの枝刈りを入れる、あるいはその両方を行う必要があると思います。参考 URL 3 によると、ナルシシスト数は全部で 88 個あり、最大桁数は 39 になるそうです。deepgreen さんは、参考 URL 1 ですべてのナルシシスト数を求めています。興味のある方は挑戦してみてください。

●参考 URL

  1. 計算パズル集, (deepgreen さん)
  2. ナルシシスト数について | 高校数学の美しい物語, (マスオさん)
  3. ナルシシスト数 -- Wikipedia

●プログラムリスト

//
// narci.js : ナルシスト数
//
// Copyright (c) 2025 Makoto Hiroi
//
// Released under the MIT license
// https://opensource.org/license/mit/
//
//
import { iota, combination_with_repetition } from './combination.js';

// 数を桁ごとに分解
function split_digit(n) {
    let a = [];
    while (n > 0n) {
        a.push(n % 10n);
        n /= 10n;
    }
    return a;
}

// 数字の個数をカウント
function count_number(xs) {
    let a = [0,0,0,0,0, 0,0,0,0,0];
    for (let x of xs) a[x]++;
    return a;
}

// 畳み込み
function fold_left2(fn, a, xs, ys) {
    let k = Math.min(xs.length, ys.length);
    for (let i = 0; i < k; i++) a = fn(a, xs[i], ys[i]);
    return a;
}

// 配列が等しいか
function equal_array(xs, ys) {
    if (xs.length != ys.length) return false;
    for (let i = 0; i < xs.length; i++) {
        if (xs[i] != ys[i]) return false;
    }
    return true;
}

// n 桁のナルシスト数を求める
function solver(n) {
    let ps = iota(0n, 10, 1n).map(x => x ** n);
    for (let xs of combination_with_repetition(n, iota(0, 10))) {
        let cs = count_number(xs);
        let x = fold_left2((a, x, y) => a += BigInt(x) * y, 0n, cs, ps);
        let ds = count_number(split_digit(x));
        if (equal_array(cs, ds)) console.log(x);
    }
}

// 実行
for (let i = 1n; i <= 18n; i++) {
    console.log(`----- ${i} -----`);
    solver(i);
}

初版 2025 年 1 月 25 日