数学をプログラミング

完全数をPythonで見つけよう!数論の不思議をプログラムで体感!

前回「完全数入門! 数の面白シリーズ!」で完全数について紹介しました。
この記事では、「完全数」という数の不思議な性質を、Pythonを使って実際にコードで体感していきます。
数学が好きな方や、プログラミングを学び始めた方に向けて、シンプルなコードで分かりやすく解説します。

完全数とは (復習)

完全数 (Perfect Number)

自身以外の正の約数の和が、ちょうど自分自身に等しい数のこと

たとえば、以下は完全数の例です。

  • 6 (1+2+3 = 6)
  • 28 (1+2+4+7+14 = 28)
  • 496
  • 8128

1から10000までの完全数を探そう

pythonでプログラムを組むことで1から10000までの完全数を列挙します。

# このコードでは、1から10000までの自然数を順に調べ、完全数かどうかを`is_perfect()`関数で判定しています。  
# 見つかった完全数だけを、`print()`関数で出力しています。

# 完全数かどうかを判定する関数
def is_perfect(n): 
    divisors = []  # 約数を保存するためのリスト
    for i in range(1, n):  # 1からn-1までの数を調べる
        if n % i == 0:     # iがnの約数なら
            divisors.append(i)  # リストに追加する
    return sum(divisors) == n  # 自分を除く約数の和がnに等しければ完全数

# プログラムの本体部分
if __name__ == '__main__':
    for i in range(2, 10001):  # 2から10000まで調べる
        if is_perfect(i): 
            print(f"{i} は完全数です")

このソースコードを実行すると次のような結果が表示されます。

6は完全数です
28は完全数です
496は完全数です
8128は完全数です

上のソースコードは完全数の定義に沿って作成しています。
is_perfect関数では次のような手順で与えられた引数nが完全数かどうかを判断しています。

  1. 空のリスト divisors を用意します。
  2. 1 から n-1 までの整数の中で、n を割り切れるもの(=約数)を探し、divisors に追加していきます。
  3. 集めた約数の合計を求め、それが n 自身と一致していれば True(完全数)を返します。そうでなければ False を返します。

プログラム本体では2から10000までの数で完全数になるものを調べています。
is_perfect関数が引数に与えた数が完全数ならTrueを返すのでif文で分岐し、完全数であるものだけを出力しています。

print(f"{i} は完全数です") のように、文字列の前に f を付けると、
中括弧 {} の中に変数や式を埋め込むことができます。これを f-string(フォーマット済み文字列リテラル) と呼びます。

(おまけ) Rustで完全数を探してみよう!

上で作成したソースコードをRustで作成します。

// 関数 is_perfect:n が完全数かどうかを判定する
fn is_perfect(n: u32) -> bool {
    // 約数を格納するベクタ(Pythonのリストに相当)
    let mut divisors = Vec::new();

    // 1 から n-1 までの数を順に調べる
    for i in 1..n {
        // i が n の約数であれば
        if n % i == 0 {
            // 約数リストに追加
            divisors.push(i);
        }
    }

    // 約数の合計を求める(Pythonの sum(divisors) に相当)
    let sum: u32 = divisors.iter().sum();

    // 合計が n に等しければ完全数
    sum == n
}

fn main() {
    // 2 から 10000 までの数を調べる
    for i in 2..=10000 {
        // i が完全数であれば表示
        if is_perfect(i) {
            println!("{} は完全数です", i);
        }
    }
}

やっていることは基本的にはPythonと同じです。
ただ、実行してみるとPythonよりRustの方が実行速度が速いことに気づくと思います。
実際にpythonとRustにそれぞれ以下を入れて計測するとRustの方が1秒ほど速いことが分かります。

import time
# 途中は同じ
if __name__ == '__main__':
    start = time.time() # 開始時間の記録
    for i in range(2,10001):# 2から10000まで調べる
        if is_perfect2(i):
            print(f"{i}は完全数です")
    end = time.time()
    print(f"かかった時間: {end - start:.2f}秒")

use std::time::Instant; // 時間計測のためのモジュールをインポート
// 途中同じ
fn main() {
    // 開始時間の記録
    let start = Instant::now();
    // 2 から 10000 までの数を調べる
    for i in 2..=10000 {
        // i が完全数であれば表示
        if is_perfect(i) {
            println!("{} は完全数です", i);
        }
    }
    // 経過時間を計算
    let duration = start.elapsed();
    println!("かかった時間: {:.2}秒", duration.as_secs_f64());
}

最後に

シンプルな定義からはじめた完全数探索ですが、これはまだ序章にすぎません。
次回の記事では、「完全数 = メルセンヌ素数 × 2のべき乗」という法則を使って、コードを改良していきます。お楽しみに!

ABOUT ME
MSK
数学・プログラミング・ゲーム制作が大好きな30代エンジニア。 趣味でUnityやC言語を使って数学を可視化したり、小さなアプリを作ったりしています。 教育・学びの楽しさにも関心があります。