M.Hiroi's Home Page

JavaScript Programming

JavaScript Junk Scripts


Copyright (C) 2025 Makoto Hiroi
All rights reserved.

deque

「双方向リスト (doubly-linked list)」と、それを使って実装した「両端キュー (deque)」のプログラムです。双方向リストの説明は拙作のページをお読みください。

●仕様

●プログラムリスト

//
// deque.js : 双方向リストとディーキュー
//
// Copyright (c) 2025 Makoto Hiroi
//
// Released under the MIT license
// https://opensource.org/license/mit/
//

// セル
class Cell {
    constructor(x, n, p) {
        this._item = x;
        this._next = n;
        this._prev = p;
    }
    get item() { return this._item; }
    set item(x) { this._item = x; }
    get next() { return this._next; }
    set next(x) { this._next = x; }
    get prev() { return this._prev; }
    set prev(x) { this._prev = x; }

    // n 個のセルをたどる
    nth(n, end = false) {
        let cp = this;
        while (n-- > 0) cp = end ? cp.prev : cp.next;
        return cp;
    }
}

// 空リストの生成
function make_empty() {
    let cp = new Cell(null, null, null);
    cp.next = cp;
    cp.prev = cp;
    return cp;
}

// 双方向リスト
class DList {
    constructor() {
        this._top = make_empty();
        this._len = 0;
    }
    get top() { return this._top; }
    get length() { return this._len; }
    set length(x) { this._len = x; }

    isEmpty() { return this._len == 0; }
    clear() {
        let header = this.top;
        header.prev = header;
        header.next = header;
        this._len = 0;
    }

    // 参照
    nth(n, end = false) {
        if (n < this.length) {
            return this.top.nth(n + 1, end).item;
        }
        return;
    }
    // 書き換え
    set(n, x, end = false) {
        if (n < this.length) {
            this.top.nth(n + 1, end).item = x;
            return x;
        }
        return;
    }
    // 挿入
    add(n, x, end = false) {
        if (n <= this.length) {
            let p = this.top.nth(n, end);
            let q = end ? p.prev : p.next;
            if (end) {
                // q - c - p
                let c = new Cell(x, p, q);
                q.next = c;
                p.prev = c;
            } else {
                // p - c - q
                let c = new Cell(x, q, p);
                p.next = c;
                q.prev = c;
            }
            this.length++;
            return x;
        }
        return;
    }
    // 削除
    delete(n, end = false) {
        if (n < this.length) {
            let c = this.top.nth(n + 1, end);
            let x = c.item;
            let p = c.next;
            let q = c.prev;
            // q - c - p
            q.next = p;
            p.prev = q;
            this.length--;
            return x;
        }
        return;
    }
    // ジェネレータ
    *makeGen(end = false) {
        let cp = this.top;
        let n = this.length;
        while (n-- > 0) {
            cp = end ? cp.prev : cp.next;
            yield cp.item;
        }
    }
    *[Symbol.iterator]() { yield* this.makeGen(); }

    //
    // 探索
    //
    find(pred, end = false) {
        for (let x of this.makeGen(end)) {
            if (pred(x)) return x;
        }
        return false;
    }
    findIndex(pred, end = false) {
        let i = 0;
        for (let x of this.makeGen(end)) {
            if (pred(x)) return i;
            i++;
        }
        return -1;
    }
    indexOf(x, end = false) {
        return this.findIndex(y => x == y, end);
    }

    //
    // 高階関数
    //
    map(fn) {
        let d = new DList();
        for (let x of this)  d.add(0, fn(x), true);
        return d;
    }
    filter(pred) {
        let d = new DList();
        for (let x of this) {
            if (pred(x)) d.add(0, x, true);
        }
        return d;
    }
    reduce(fn, a, end = false) {
        for (let x of this.makeGen(end)) {
            a = fn(a, x);
        }
        return a;
    }
    forEach(fn, end = false) {
        for (let x of this.makeGen(end))  fn(x);
    }

    // 文字列
    toString() {
      return "(" + [...this].join(",") + ")";
    }

    // スタティックメソッド
    static from(iter) {
        let d = new DList();
        for (let x of iter) d.add(0, x, true);
        return d;
    }
    static of(...args) { return DList.from(args); }
}

// 両端キュー
class Deque {
    constructor() {
        this._que = new DList();
    }
    get que() { return this._que; }

    isEmpty() { return this.que.isEmpty(); }
    length()  { return this.que.length; }
    clear()   { this.que.clear(); }

    push_front(x) { return this.que.add(0, x); }
    pop_front()   { return this.que.delete(0); }
    peek_front()  { return this.que.nth(0); }
    push_back(x)  { return this.que.add(0, x, true); }
    pop_back()    { return this.que.delete(0, true); }
    peek_back()   { return this.que.nth(0, true); }
    toString()    { return this.que.toString(); }
}

export { DList, Deque };

●簡単なテスト

//
// test_deque.js : 双方向リストとディーキューのテスト
//
// Copyright (c) 2025 Makoto Hiroi
//
// Released under the MIT license
// https://opensource.org/license/mit/
//

import { DList, Deque } from './deque.js';

var a = new DList();
console.log('%s', a);       // ()
console.log(a.isEmpty());   // true
console.log(a.length);      // 0
for (let i = 1; i <= 4; i++) {
    a.add(0, i);
}
console.log('%s', a);       // (4,3,2,1)
console.log(a.isEmpty());   // false
console.log(a.length);      // 4
for (let i = 5; i <= 8; i++) {
    a.add(0, i, true);
}
console.log('%s', a);       // (4,3,2,1,5,6,7,8)
console.log(a.isEmpty());   // false
console.log(a.length);      // 8
for (let i = 0; i < a.length; i++) console.log(a.nth(i))
for (let i = 0; i < a.length; i++) console.log(a.nth(i, true))
for (let i = 0; i < a.length; i++) a.set(i, a.nth(i) + i);
console.log('%s', a);       // 4,4,4,4,9,11,13,15
for (let i = 0; i < a.length; i++) a.set(i, a.nth(i, true) + i, true);
console.log('%s', a);       // 11,10,9,8,12,13,14,15
console.log(a.delete(0));   // 11
console.log(a.delete(3));   // 12
console.log(a.delete(0, true));  // 15
console.log(a.delete(3, true));  // 9
console.log(a.length)       // 4
console.log('%s', a);       // 10, 8, 13, 14
a.clear();
console.log(a.isEmpty());   // true
console.log(a.length);      // 0
for (let i = 1; i <= 8; i++) a.add(0, i, true);
for (let x of a) console.log(x); // 1,2,3,4,5,6,7,8
for (let x of a.makeGen(true)) console.log(x);  // 8,7,6,5,4,3,2,1
console.log(a.find(x => x % 2 == 0));         // 2
console.log(a.find(x => x % 2 == 0, true));   // 8
console.log(a.find(x => x > 8));                // false
console.log(a.findIndex(x => x % 2 == 0));         // 1
console.log(a.findIndex(x => x % 2 == 0, true));   // 0
console.log(a.findIndex(x => x > 8));              // -1
console.log(a.indexOf(2));         // 1
console.log(a.indexOf(8, true));   // 0
console.log(a.indexOf(9));         // -1
console.log('%s', a.map(x => x * x));  // 1 4 9 ...
console.log('%s', a.filter(x => x % 2 == 0));  // 2,4,6,8
console.log(a.reduce((a, x) => a + x, 0));           // 36
console.log(a.reduce((a, x) => a + x, 0, true));     // 36
a.forEach(console.log);
a.forEach(console.log, true);
console.log('%s', DList.from([1,2,3,4,5,6,7,8]));
console.log('%s', DList.of(1,2,3,4,5,6,7,8));

// 両端キュー
console.log("----- Deque -----");
var b = new Deque();
console.log('%s', b);      // ()
console.log(b.isEmpty());  // true
console.log(b.length());     // 0
for (let i = 1; i <= 4; i++) b.push_front(i);
console.log('%s', b);      // (4,3,2,1)
console.log(b.isEmpty());  // false
console.log(b.length());     // 4
for (let i = 1; i <= 4; i++) console.log(b.pop_front()); // 4 3 2 1
console.log('%s', b);      // ()
console.log(b.isEmpty());  // true
console.log(b.length());     // 0
for (let i = 1; i <= 4; i++) b.push_back(i);
console.log('%s', b);      // (1,2,3,4)
console.log(b.isEmpty());  // false
console.log(b.length());     // 4
for (let i = 1; i <= 4; i++) console.log(b.pop_back()); // 4 3 2 1
console.log('%s', b);      // ()
console.log(b.isEmpty());  // true
console.log(b.length());   // 0
for (let i = 1; i <= 8; i++) b.push_back(i);
console.log('%s', b);      // (1,2,3,4,5,6,7,8
console.log(b.isEmpty());  // false
console.log(b.length());   // 8
console.log(b.peek_front());  // 1
console.log(b.peek_back());   // 8
while (!b.isEmpty()) console.log(b.pop_front());  // 1 2 3 4 ...

●実行結果

$ node test_deque.js
()
true
0
(4,3,2,1)
false
4
(4,3,2,1,5,6,7,8)
false
8
4
3
2
1
5
6
7
8
8
7
6
5
1
2
3
4
(4,4,4,4,9,11,13,15)
(11,10,9,8,12,13,14,15)
11
12
15
9
4
(10,8,13,14)
true
0
1
2
3
4
5
6
7
8
8
7
6
5
4
3
2
1
2
8
false
1
0
-1
1
0
-1
(1,4,9,16,25,36,49,64)
(2,4,6,8)
36
36
1
2
3
4
5
6
7
8
8
7
6
5
4
3
2
1
(1,2,3,4,5,6,7,8)
(1,2,3,4,5,6,7,8)
----- Deque -----
()
true
0
(4,3,2,1)
false
4
4
3
2
1
()
true
0
(1,2,3,4)
false
4
4
3
2
1
()
true
0
(1,2,3,4,5,6,7,8)
false
8
1
8
1
2
3
4
5
6
7
8

●サンプルプログラム (経路の探索)

    B───D───F 
  /│      │
A  │      │
  \│      │
    C───E───G

    図 : 経路図
//
// keiro_deque.js : 経路の探索 (deque のサンプル)
//
// Copyright (c) 2025 Makoto Hiroi
//
// Released under the MIT license
// https://opensource.org/license/mit/
//
import { DList, Deque } from './deque.js';

//      1 ----- 3 ----- 5
//    / |       |
//  0   |       |
//    \ |       |
//      2 ----- 4 ----- 6

// 隣接リスト
const adjacent = [
    [1, 2],     // 0
    [0, 2, 3],  // 1
    [0, 1, 4],  // 2
    [1, 4, 5],  // 3
    [2, 3, 6],  // 4
    [3],        // 5
    [4]         // 6
];

// 深さ優先探索
function dfs(goal, path) {
    let p = path.nth(0, true);
    if (p == goal) {
        console.log('%s', path);
    } else {
        for (let q of adjacent[p]) {
            if (path.indexOf(q) == -1) {
                path.add(0, q, true);
                dfs(goal, path);
                path.delete(0, true);
            }
        }
    }
}

// 実行
console.log('----- depth first search -----');
dfs(6, DList.of(0));

// 幅優先探索
function bfs(start, goal) {
    let q = new Deque();
    q.push_back(DList.of(start));
    while (!q.isEmpty()) {
        let xs = q.pop_front();
        let x = xs.nth(0, true);
        if (x == goal) {
            console.log('%s', xs);
        } else {
            for (let y of adjacent[x]) {
                if (xs.indexOf(y) == -1) {
                    let ys = DList.from([...xs]);
                    ys.add(0, y, true);
                    q.push_back(ys);
                }
            }
        }
    }
}

console.log('----- breadth first search -----');
bfs(0, 6);

// 反復深化
function dfsi(path, goal, limit) {
    if (path.length == limit) {
        if (path.nth(0, true) == goal) {
            console.log('%s', path);
        }
    } else {
        for (let p of adjacent[path.nth(0, true)]) {
            if (path.indexOf(p) == -1) {
                path.add(0, p, true);
                dfsi(path, goal, limit);
                path.delete(0, true);
            }
        }
    }
}

function ids(start, goal) {
    for (let limit = 1; limit <= adjacent.length; limit++) {
        console.log(`----- ${limit} -----`);
        dfsi(DList.of(start), goal, limit);
    }
}

console.log('----- ids -----');
ids(0, 6);

●実行結果

$ node keiro_dlist.js
----- depth first search -----
(0,1,2,4,6)
(0,1,3,4,6)
(0,2,1,3,4,6)
(0,2,4,6)
----- breadth first search -----
(0,2,4,6)
(0,1,2,4,6)
(0,1,3,4,6)
(0,2,1,3,4,6)
----- ids -----
----- 1 -----
----- 2 -----
----- 3 -----
----- 4 -----
(0,2,4,6)
----- 5 -----
(0,1,2,4,6)
(0,1,3,4,6)
----- 6 -----
(0,2,1,3,4,6)
----- 7 -----

ソート

整列 (sorting) は、ある規則に従ってデータを順番に並べ換える操作です。たとえば、データが整数であれば大きい順に並べる、もしくは小さい順に並べます。一般に、このような操作を「ソート」と呼んでいます。ソートは昔から研究されている分野で、優秀なアルゴリズムが確立しています。

ソートは大きく分けると、内部ソート (internal sort) と外部ソート (external sort) にわかれます。内部ソートはすべてのデータをメモリに読み込んでソートします。外部ソートはメモリにすべて読み込むことができない巨大なデータをソートするときに使われ、外部記憶装置に途中経過を記憶させながらソートします。

JavaScript の配列 (と型付き配列) にはソートを行うメソッド sort() がありますが、私達でもプログラムすることができます。ここでは代表的なソートアルゴリズムをいくつか実装してみましょう。アルゴリズムの詳しい説明は拙作のページをお読みください。

●プログラムリスト

//
// sort.js : ソート
//
// Copyright (c) 2025 Makoto Hiroi
//
// Released under the MIT license
// https://opensource.org/license/mit/
//

function make_data(x) {
    // x は生成するデータの個数
    // 乱数
    let a = new Float64Array(x);
    for (let i = 0; i < x; i++) a[i] = Math.random();
    // 昇順
    let b = new Float64Array(x);
    for (let i = 0; i < x; i++) b[i] = i + 1.0;
    // 降順
    let c = new Float64Array(x);
    for (let i = 0; i < x; i++) c[i] = x - i;
    // 山型
    let d = new Float64Array(x);
    let m = Math.floor(x / 2);
    for (let i = 0; i < m; i++) d[i] = i + 1.0;
    for (let i = m; i < x; i++) d[i] = x - i;
    return [a, b, c, d];
}

function check(xs) {
    for (let i = 0; i < xs.length - 1; i++) {
        if (xs[i] > xs[i + 1]) throw new Error("sort error");
    }
}

function test(func, xs) {
    for (let x of xs) {
        let rs = [];
        for (let ys of make_data(x)) {
            let s = new Date().getTime();
            func(ys);
            let e = new Date().getTime();
            rs.push(e - s);
            check(ys);
        }
        console.log(x, ":", rs);
    }
}

// メソッド sort() の時間計測
function test1(xs) {
    for (let x of xs) {
        let rs = [];
        for (let ys of make_data(x)) {
            let s = new Date().getTime();
            ys.sort();
            let e = new Date().getTime();
            rs.push(e - s);
            check(ys);
        }
        console.log(x, ":", rs);
    }
}

// バブルソート
function buble_sort(buff) {
    let k = buff.length - 1;
    for (let i = 0; i < k; i++) {
        for (let j = k; j > i; j--) {
            if (buff[j - 1] > buff[j]) {
                let temp = buff[j];
                buff[j] = buff[j - 1];
                buff[j - 1] = temp;
            }
        }
    }
    return buff;
}

// 単純挿入ソート
function insert_sort(buff) {
    let k = buff.length;
    for (let i = 1; i < k; i++) {
        let temp = buff[i];
        let j = i - 1;
        for (; j >=0 && temp < buff[j]; j--) {
            buff[j + 1] = buff[j];
        }
        buff[j + 1] = temp;
    }
    return buff;
}

// シェルソート
function shell_sort(buff) {
    let k = buff.length;
    let gap = 1;
    while (gap < Math.floor(k / 9)) gap = gap * 3 + 1;
    while (gap > 0) {
        for (let i = gap; i < k; i++) {
            let temp = buff[i];
            let j = i - gap;
            while (j >= 0 && temp < buff[j]) {
                buff[j + gap] = buff[j];
                j -= gap;
            }
            buff[j + gap] = temp;
        }
        gap = Math.floor(gap / 3);
    }
    return buff;
}

// クイックソート (単純版)
function qsort(buff, low, high) {
    let pivot = buff[low + Math.floor((high - low)/2)];
    let i = low;
    let j = high;
    while (true) {
        while (pivot > buff[i]) i++;
        while (pivot < buff[j]) j--;
        if (i >= j) break;
        let temp = buff[i];
        buff[i] = buff[j];
        buff[j] = temp;
        i++;
        j--;
    }
    if (low < i - 1) qsort(buff, low, i - 1);
    if (high > j + 1) qsort(buff, j + 1, high);
}

function quick_sort(buff) {
    qsort(buff, 0, buff.length - 1);
    return buff;
}

// 改良版

// 枢軸の選択 (median-of-3)
function select_pivot(buff, low, high) {
    let a = buff[low];
    let b = buff[low + Math.floor((high - low) / 2)];
    let c = buff[high];
    if (a > b) [a, b] = [b, a];
    if (b > c) {
        b = c;
        if (a > b) b = a;
    }
    return b;
}

const limit = 10;

function qsort2(buff, low, high) {
    let stack = [];
    while (true) {
        if ((high - low) <= limit) {
            if (stack.length == 0) break;
            [low, high] = stack.pop();
        } else {
            let pivot = select_pivot(buff, low, high)
            let i = low
            let j = high
            while (true) {
                while (pivot > buff[i]) i++;
                while (pivot < buff[j]) j--;
                if (i >= j) break;
                let temp = buff[i];
                buff[i] = buff[j];
                buff[j] = temp;
                i++;
                j--;
            }
            if (i - low > high - j) {
                stack.push([low, i - 1]);
                low = j + 1;
            } else {
                stack.push([j + 1, high]);
                high = i - 1;
            }
        }
    }
    insert_sort(buff)
}

function quick_sort2(buff) {
    qsort2(buff, 0, buff.length - 1);
    return buff;
}

●実行結果

> test(buble_sort, [10000,20000,40000,80000])
10000 : [ 215, 83, 115, 100 ]
20000 : [ 888, 332, 466, 402 ]
40000 : [ 3790, 1315, 1850, 1599 ]
80000 : [ 15422, 5283, 7385, 6336 ]
undefined

> test(insert_sort, [10000,20000,40000,80000])
10000 : [ 45, 0, 83, 41 ]
20000 : [ 174, 0, 328, 164 ]
40000 : [ 667, 1, 1318, 662 ]
80000 : [ 2641, 0, 5302, 2643 ]
undefined
> test(shell_sort, [10000,20000,40000,80000])
10000 : [ 7, 1, 0, 1 ]
20000 : [ 4, 0, 1, 1 ]
40000 : [ 8, 3, 2, 3 ]
80000 : [ 18, 5, 6, 5 ]
undefined
> test(shell_sort, [100000,200000,400000,800000])
100000 : [ 22, 5, 7, 7 ]
200000 : [ 49, 12, 14, 13 ]
400000 : [ 108, 25, 31, 30 ]
800000 : [ 243, 54, 66, 64 ]
undefined
> test(quick_sort, [10000,20000,40000,80000])
10000 : [ 2, 0, 0, 77 ]
Uncaught RangeError: Maximum call stack size exceeded
    at qsort (REPL4:99:15)
    at qsort (REPL4:113:22)
    at qsort (REPL4:113:22)
    at qsort (REPL4:113:22)
    at qsort (REPL4:113:22)
    at qsort (REPL4:113:22)
    at qsort (REPL4:113:22)
    at qsort (REPL4:113:22)
    at qsort (REPL4:113:22)
> test(quick_sort2, [10000,20000,40000,80000])
10000 : [ 10, 1, 1, 1 ]
20000 : [ 5, 2, 1, 3 ]
40000 : [ 5, 1, 1, 5 ]
80000 : [ 12, 3, 4, 11 ]
undefined
> test(quick_sort2, [100000,200000,400000,800000])
100000 : [ 14, 4, 4, 15 ]
200000 : [ 30, 7, 9, 31 ]
400000 : [ 63, 15, 16, 68 ]
800000 : [ 181, 33, 34, 152 ]
undefined
> test1([100000, 200000, 400000, 800000])
100000 : [ 13, 5, 5, 17 ]
200000 : [ 24, 12, 9, 35 ]
400000 : [ 53, 27, 19, 75 ]
800000 : [ 110, 54, 40, 159 ]
undefined

約数

約数 (divisor) は整数 n を割り切る整数のことです。たとえば、24 の正の約数は 1, 2, 3, 4, 6, 8, 12, 24 の 8 個あります。ここでは正の約数を考えることにします。

正の約数が 1 と自分自身しかない自然数のことを素数といいます。1 ではない正整数で、素数ではないものを合成数といいます。合成数は素数の積の形に書き表すことができます。これを「素因数分解 (prime factorization)」といいます。

たとえば、6 = 2 * 3, 12 = 22 * 3, 30 = 2 * 3 * 5 と素因数分解することができます。素数の並べ方を除けば、素因数分解はただの一通りしかありません。これを「素因数分解の一意性」といいます。

●問題

  1. 自然数 n を素因数分解する関数 factorization(n) を作ってください。
  2. 自然数 n の約数の個数を求める関数 divisor_count(n) を作ってください。
  3. 自然数 n の約数の和を求める関数 divisor_total(n) を作ってください。
  4. 自然数 n の約数をすべて求める関数 divisors(n) を作ってください。
  5. 『完全数(かんぜんすう,perfect number)とは、その数自身を除く約数の和が、その数自身と等しい自然数のことである。』 とのことです。10000 以下の完全数を求めてください。
  6. 『友愛数(ゆうあいすう)とは、異なる2つの自然数の組で、自分自身を除いた約数の和が、互いに他方と等しくなるような数をいう。』 とのことです。100000 以下の友愛数を求めてください。
  7. 自然数 n のすべての約数を k 乗し、その総和を求める関数 divisor_sigma(n, k) を定義してください。k = 0 は約数の個数、k = 1 は約数の和になります。
  8. 整数 n に対して、n 以下の自然数で n と互いに素となる個数を求める関数 totient(n) を定義してください。
    たとえば、1, 2, 3, 4 の中で 4 と互いに素となる数は 1 と 3 の 2 つあるので、totient(4) は 2 になります。
    totient(5) は、5 が素数なので、1, 2, 3, 4 と 5 は互いに素となるため、totient(5) は 4 になります。

詳しい説明は拙作のページをお読みください。

●プログラムリスト

//
// divisor.js : 約数
//
// Copyright (c) 2025 Makoto Hiroi
//
// Released under the MIT license
// https://opensource.org/license/mit/
//

// 素因数分解
function factor_sub(n, p){
    let c = 0n;
    while (n % p == 0n) {
        n /= p;
        c++;
    }
    return [c, n]
}

function factorization(n) {
    let ps = [];
    let wheel = [1n, 2n, 2n, 4n, 2n, 4n, 2n, 4n, 6n, 2n, 6n];
    let [i, s] = [0, 3n];
    let [x, c, m] = [2n, 0n, n];
    while (m >= x * x) {
        [c, m] = factor_sub(m, x);
        if (c > 0n) ps.push([x, c])
        x += wheel[i]
        if (++i >= wheel.length) i = s;
    }
    if (m > 1n) ps.push([m, 1n]);
    return ps;
}

// 約数の個数
function divisor_count(n) {
    return factorization(n).reduce((a, [p, q]) => a * (q + 1n), 1n);
}

// 約数の合計値
function divisor_total(n) {
    return factorization(n).reduce((a, [p, q]) => a * (p ** (q + 1n) - 1n) / (p - 1n), 1n);
}

// p ** q の約数を求める
function divisor_sub(p, q) {
    let a = [];
    for (let i = 0n; i <= q; i++) a.push(p ** i);
    return a;
}

// 直積集合
function product(xs, ys) {
    let a = [];
    for (let x of xs) {
        for (let y of ys) a.push(x * y);
    }
    return a;
}

// 比較
function compare(a, b) {
    if (a > b) return 1;
    if (a == b) return 0
    return -1;
}

// 約数を求める
function divisors(n) {
    let xs = factorization(n);
    return xs.reduce((a, [p, q]) => product(a, divisor_sub(p, q)), [1n]).sort(compare);
}

// 完全数
function perfect_number(n) {
    for (let x = 2n; x <= n; x++) {
        if (divisor_total(x) - x == x) {
            console.log('%s', x);
        }
    }
}

// 友愛数
function amicable_number(n) {
    for (let x = 2n; x <= n; x++) {
        let m = divisor_total(x) - x;
        if (m < x && x == divisor_total(m) - m) {
            console.log('%s %s', m, x);
        }
    }
}

// 約数関数
function divisor_sigma(n, k) {
    if (k == 0) return divisor_count(n);
    let xs = factorization(n);
    return xs.reduce((a, [p, q]) => a * (p ** (k * (q + 1n)) - 1n) / (p ** k - 1n), 1n);
}

// オイラーのトーシェント関数 (公式)
function totient(n) {
    let xs = factorization(n);
    return xs.reduce((d, [p, q]) => d * (p - 1n) / p, n);
}

●実行例

> var a = [24n, 12345678n, 123456789n, 1234567890n, 1111111111n]
undefined

> for (let x of a) console.log(x, factorization(x));
24n [ [ 2n, 3n ], [ 3n, 1n ] ]
12345678n [ [ 2n, 1n ], [ 3n, 2n ], [ 47n, 1n ], [ 14593n, 1n ] ]
123456789n [ [ 3n, 2n ], [ 3607n, 1n ], [ 3803n, 1n ] ]
1234567890n [ [ 2n, 1n ], [ 3n, 2n ], [ 5n, 1n ], [ 3607n, 1n ], [ 3803n, 1n ] ]
1111111111n [ [ 11n, 1n ], [ 41n, 1n ], [ 271n, 1n ], [ 9091n, 1n ] ]
undefined

> for (let x of a) console.log(x, divisor_count(x));
24n 8n
12345678n 24n
123456789n 12n
1234567890n 48n
1111111111n 16n
undefined

> for (let x of a) console.log(x, divisor_total(x));
24n 60n
12345678n 27319968n
123456789n 178422816n
1234567890n 3211610688n
1111111111n 1246404096n
undefined

> for (let x of a) console.log(x, divisors(x))
24n [
  1n, 2n,  3n,  4n,
  6n, 8n, 12n, 24n
]
12345678n [
        1n,       2n,        3n,
        6n,       9n,       18n,
       47n,      94n,      141n,
      282n,     423n,      846n,
    14593n,   29186n,    43779n,
    87558n,  131337n,   262674n,
   685871n, 1371742n,  2057613n,
  4115226n, 6172839n, 12345678n
]
123456789n [
         1n,         3n,
         9n,      3607n,
      3803n,     10821n,
     11409n,     32463n,
     34227n,  13717421n,
  41152263n, 123456789n
]
1234567890n [
          1n,         2n,         3n,          5n,
          6n,         9n,        10n,         15n,
         18n,        30n,        45n,         90n,
       3607n,      3803n,      7214n,       7606n,
      10821n,     11409n,     18035n,      19015n,
      21642n,     22818n,     32463n,      34227n,
      36070n,     38030n,     54105n,      57045n,
      64926n,     68454n,    108210n,     114090n,
     162315n,    171135n,    324630n,     342270n,
   13717421n,  27434842n,  41152263n,   68587105n,
   82304526n, 123456789n, 137174210n,  205761315n,
  246913578n, 411522630n, 617283945n, 1234567890n
]
1111111111n [
          1n,         11n,
         41n,        271n,
        451n,       2981n,
       9091n,      11111n,
     100001n,     122221n,
     372731n,    2463661n,
    4100041n,   27100271n,
  101010101n, 1111111111n
]
undefined

> perfect_number(10000n)
6n
28n
496n
8128n
undefined

> amicable_number(100000n)
220n 284n
1184n 1210n
2620n 2924n
5020n 5564n
6232n 6368n
10744n 10856n
12285n 14595n
17296n 18416n
66928n 66992n
67095n 71145n
63020n 76084n
69615n 87633n
79750n 88730n
undefined

> for (let x of a) console.log(x, divisor_sigma(x, 0n))
24n 8n
12345678n 24n
123456789n 12n
1234567890n 48n
1111111111n 16n
undefined
> for (let x of a) console.log(x, divisor_sigma(x, 1n))
24n 60n
12345678n 27319968n
123456789n 178422816n
1234567890n 3211610688n
1111111111n 1246404096n
undefined
> for (let x of a) console.log(x, divisor_sigma(x, 2n))
24n 850n
12345678n 214137553857500n
123456789n 17123257639169500n
1234567890n 2226023493092035000n
1111111111n 1245528410223519376n
undefined

> for (let x of a) console.log(x, totient(x))
24n 8n
12345678n 4027392n
123456789n 82260072n
1234567890n 329040288n
1111111111n 981720000n

初版 2025 年 2 月 2, 5, 8 日