Pythonで素数判定を行う方法を初心者向けに実例6選で解説

Python初心者が素数判定を学ぶためのガイドブックPython
この記事は約21分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

●素数とは?

数学においては、素数は非常に重要な概念です。

素数とは、1とその数自身以外に正の約数を持たない自然数のことを指します。

言い換えれば、1より大きい自然数で、1とその数自身でしか割り切れない数が素数なのです。

2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97…

このように、素数は無限に存在しています。

素数は、数論における基本的な構成要素であり、暗号理論や計算機科学などの分野でも重要な役割を果たしています。

○素数判定の基本

素数を判定するための最も単純な方法は、2からその数の平方根までの整数で割ってみることです。

割り切れる数が存在しなければ、その数は素数であると判断できます。

では、なぜ平方根まで調べれば十分なのでしょうか?

それは、もしある数nがaとbの積で表せるとすると、aとbの少なくとも一方はn の平方根以下になるからです。

例えば、24は素数ではありません。24 = 2 × 12 = 3 × 8 = 4 × 6 と表せますが、これらの因数のペアでは必ず一方が√24以下になっています。

したがって、2からその数の平方根までの整数で割り切れなければ、それより大きな数でも割り切れないことになります。

○サンプルコード1:最も基本的な素数判定

では、Pythonを使って素数判定を行う最も基本的なコードを見てみましょう。

def is_prime(n):
    if n <= 1:
        return False
    for i in range(2, int(n**0.5) + 1):
        if n % i == 0:
            return False
    return True

このコードでは、まず引数nが1以下の場合は素数ではないのでFalseを返します。

次に、2からnの平方根までの整数iでnを割ります。

割り切れる数が見つかった場合はFalseを返し、最後までループが終了した場合はTrueを返します。

実行結果を確認してみましょう。

print(is_prime(1))   # False
print(is_prime(2))   # True
print(is_prime(3))   # True
print(is_prime(4))   # False
print(is_prime(5))   # True
print(is_prime(10))  # False
print(is_prime(11))  # True

このように、与えられた数が素数かどうかを正しく判定できていることがわかります。

ただし、このコードは非常にシンプルな実装であり、効率性の面ではまだ改善の余地があります。

●初心者向けのシンプルな素数判定法

さて、先ほどの基本的な素数判定のコードを見ていただきましたが、少しわかりにくい部分もあると思うので、ここではもう少し丁寧に解説していきたいと思います。

素数判定を行う上で、まず理解しておきたいのが「割り切れる」という概念です。

ある数nが別の数aで割り切れるとは、n ÷ a の余りが0になることを指します。例えば、12は3で割り切れますが、5では割り切れません。

素数の定義は「1とその数自身以外で割り切れない自然数」でしたね。

ということは、素数判定をするには、2からその数より1つ小さい数までで割り算を行い、割り切れる数がないかを確認すればよいということになります。

○サンプルコード2:素数判定の基本形

では、実際にPythonでこの考え方を実装してみましょう。

def is_prime(n):
    if n <= 1:
        return False
    for i in range(2, n):
        if n % i == 0:
            return False
    return True

このコードは、先ほどの基本的な素数判定のコードとほぼ同じですが、ループの終了条件がn-1までになっています。

つまり、2からn-1までのすべての数で割り算を行い、割り切れる数がなければTrueを返すという処理です。

実行結果を見てみましょう。

print(is_prime(1))   # False
print(is_prime(2))   # True
print(is_prime(3))   # True
print(is_prime(4))   # False
print(is_prime(5))   # True
print(is_prime(10))  # False
print(is_prime(11))  # True

先ほどと同じように、正しく素数判定ができていることがわかります。

ただ、この方法では効率があまりよくありません。

例えば、100が素数かどうかを判定するには、2から99までの98回の割り算が必要になります。

数が大きくなればなるほど、無駄な計算が増えてしまうのです。

そこで、もう少し効率的な素数判定のアルゴリズムを考えていく必要があります。

●効率的な素数判定アルゴリズム

前章では、初心者向けのシンプルな素数判定法を紹介しましたが、大きな数に対しては効率が悪くなってしまうという課題がありました。

そこで、ここからは少し工夫を加えて、より効率的な素数判定のアルゴリズムを考えていきましょう。

プログラミングの世界では、アルゴリズムの効率を改善することが非常に重要です。

特に、大量のデータを扱う場合や、複雑な計算を行う場合には、アルゴリズムの選択が処理速度に大きな影響を与えます。

素数判定においても、数が大きくなるにつれて計算量が増大するため、効率的なアルゴリズムを使うことが求められます。

そこで、まずは先ほどのコードを少し改良してみましょう。

○サンプルコード3:改良された素数判定法

先ほどの基本的な素数判定法では、2からn-1までのすべての数で割り算を行っていましたが、ここではいくつかの工夫を加えて、無駄な計算を省略していきます。

def is_prime(n):
    if n <= 1:
        return False
    if n == 2:
        return True
    if n % 2 == 0:
        return False
    for i in range(3, int(n**0.5) + 1, 2):
        if n % i == 0:
            return False
    return True

このコードでは、いくつかの条件分岐を追加することで、無駄な計算を省いています。

まず、nが2以下の場合は、1以下ならFalse、2ならTrueを返すように処理しています。

そして、nが偶数の場合は、2以外の偶数は素数ではないのでFalseを返します。

さらに、ループの部分では、range()の第三引数に2を指定することで、奇数のみを調べるようにしています。

これは、偶数で割り切れない数は、奇数でも割り切れないことを利用した工夫です。

実行結果を確認してみましょう。

print(is_prime(1))    # False
print(is_prime(2))    # True
print(is_prime(3))    # True
print(is_prime(4))    # False
print(is_prime(5))    # True
print(is_prime(100))  # False
print(is_prime(101))  # True

先ほどと同じ結果が得られていますが、計算量が大幅に減っているはずです。

特に、大きな数に対しては、この改良の効果が顕著に現れます。

ただ、これでも十分に効率的とは言えません。

次に紹介するのは、もっと高速な素数判定アルゴリズムの一つ、「エラトステネスの篩」です。

○サンプルコード4:エラトステネスの篩

エラトステネスの篩は、2からnまでの全ての数に対して、素数かどうかを一度に判定する非常に効率的なアルゴリズムです。

このアルゴリズムを使えば、素数のリストを高速に生成することができます。

def sieve_of_eratosthenes(n):
    prime = [True for i in range(n+1)]
    prime[0] = False
    prime[1] = False
    for p in range(2, int(n**0.5) + 1):
        if prime[p]:
            for i in range(p*p, n+1, p):
                prime[i] = False
    return [p for p in range(n+1) if prime[p]]

エラトステネスの篩は、2からnまでの全ての数に対して、素数かどうかを判定するアルゴリズムです。

まず、長さn+1のリストprimeを作成し、初期値をすべてTrueにします。

このリストの各要素は、インデックスに対応する数が素数かどうかを表します。

次に、0と1は素数ではないので、prime[0]とprime[1]をFalseにします。

そして、2からnの平方根までの数pに対して、pが素数であれば、p*pからnまでのpの倍数を全てFalseにします。

これにより、pの倍数は素数ではないとマークされます。

最後に、リスト内包表記を使って、Trueになっているインデックス(つまり素数)のリストを返します。

実行結果を見てみましょう。

print(sieve_of_eratosthenes(10))   # [2, 3, 5, 7]
print(sieve_of_eratosthenes(20))   # [2, 3, 5, 7, 11, 13, 17, 19]
print(sieve_of_eratosthenes(100))  # [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]

エラトステネスの篩を使えば、指定された範囲内の素数を一度に列挙することができます。

これは、単に素数かどうかを判定するだけでなく、素数のリストが必要な場合に特に有効です。

もちろん、エラトステネスの篩にも限界はあります。

nが非常に大きい場合は、メモリの使用量が増大してしまいます。

そのような場合には、別のアルゴリズムを検討する必要があるでしょう。

●Pythonでの素数判定の応用例

さて、ここまで素数判定のための様々なアルゴリズムを学んできましたが、実際のプログラミングではどのように活用できるのでしょうか。

プログラミングの醍醐味は、学んだ知識を実践で応用することにあります。

素数判定は、数学やセキュリティの分野で重要な役割を果たしています。

例えば、暗号化アルゴリズムの中には、素数の性質を利用したものがあります。

また、素数を使ったパズルやゲームを作成することもできます。

ここでは、Pythonで素数判定を応用する具体的な例をいくつか見ていきましょう。

これらの例を通して、素数判定がどのように実際のプログラミングで役立つのかを理解していただければと思います。

○サンプルコード5:範囲内の素数を列挙

先ほど学んだエラトステネスの篩を使えば、ある範囲内の素数を一度に列挙することができます。

では、実際にコードを書いてみましょう。

def primes_in_range(start, end):
    primes = sieve_of_eratosthenes(end)
    return [p for p in primes if p >= start]

# 1から100までの素数を列挙
print(primes_in_range(1, 100))
# [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]

# 50から150までの素数を列挙
print(primes_in_range(50, 150))
# [53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149]

このコードでは、sieve_of_eratosthenes()関数を使って、まず終了値までの素数のリストを作成しています。

そして、リスト内包表記を使って、開始値以上の素数だけを取り出しています。

このように、ある範囲内の素数を簡単に列挙することができます。

これは、例えば素数を使った問題を解く際に便利です。

○サンプルコード6:特定の条件の素数を探索

素数には、特定の条件を満たすものがあります。

例えば、「双子素数」と呼ばれる、差が2である素数のペアがあります。

(3, 5), (5, 7), (11, 13), (17, 19), (29, 31)などがその例です。

また、「メルセンヌ素数」と呼ばれる、2^p – 1の形で表される素数もあります。

pが素数のとき、2^p – 1が素数になる場合があるのです。

では、双子素数とメルセンヌ素数を探索するコードを書いてみましょう。

def is_twin_prime(p):
    return is_prime(p) and (is_prime(p-2) or is_prime(p+2))

def is_mersenne_prime(p):
    return is_prime(p) and is_prime(2**p - 1)

# 双子素数を探索
for p in range(2, 100):
    if is_twin_prime(p):
        print(p, "is a twin prime")

# メルセンヌ素数を探索
for p in range(2, 20):
    if is_mersenne_prime(p):
        print(2**p - 1, "is a Mersenne prime")

実行結果は次のようになります。

3 is a twin prime
5 is a twin prime
7 is a twin prime
11 is a twin prime
13 is a twin prime
17 is a twin prime
19 is a twin prime
29 is a twin prime
31 is a twin prime
41 is a twin prime
43 is a twin prime
59 is a twin prime
61 is a twin prime
71 is a twin prime
73 is a twin prime
3 is a Mersenne prime
7 is a Mersenne prime
31 is a Mersenne prime
127 is a Mersenne prime
8191 is a Mersenne prime
131071 is a Mersenne prime
524287 is a Mersenne prime

このように、素数判定を応用することで、特定の性質を持つ素数を探索することができます。

数学的な問題を解く際に、このような探索が必要になることがあります。

●よくあるエラーと対処法

プログラミングを学ぶ過程では、誰もが様々なエラーに遭遇します。

特に初心者の方は、コードの書き方や解釈の間違いから、思わぬエラーに悩まされることがあるでしょう。

でも、そんなエラーも、原因を理解し、適切に対処できれば、むしろ学びの糧になります。

ここでは、素数判定のコードを書く際によくあるエラーをいくつか取り上げ、その原因と対処法を説明します。

エラーを恐れずに、積極的にコードを書いて試行錯誤することが、プログラミングスキルの向上につながります。

○エラー1:コードの解釈間違いとその修正

初心者がよく陥るエラーの一つに、コードの解釈間違いがあります。

例えば、次のような間違ったコードを見てみましょう。

def is_prime(n):
    if n <= 1:
        return False
    for i in range(2, n):
        if n / i == 0:
            return False
    return True

このコードの意図は、nが素数かどうかを判定することですが、if文の条件が間違っています。

正しくは、n % i == 0とすべきです。n / i == 0では、整数の割り算の結果が0になることはありません。

このようなエラーは、コードの動作を注意深く考えることで防ぐことができます。

コードを書いた後は、各行が意図した通りに動作するか、しっかりと確認しましょう。

修正後のコードは下記のようになります。

def is_prime(n):
    if n <= 1:
        return False
    for i in range(2, n):
        if n % i == 0:
            return False
    return True

○エラー2:効率性の低いアルゴリズムの改善

次に、コードは正しく動作するが、効率性が低いために実用的でないケースを見てみましょう。

def is_prime(n):
    if n <= 1:
        return False
    for i in range(2, n):
        if n % i == 0:
            return False
    return True

def nth_prime(n):
    count = 0
    num = 2
    while count < n:
        if is_prime(num):
            count += 1
        num += 1
    return num - 1

このコードは、n番目の素数を求めるものですが、効率があまりよくありません。

is_prime()関数が、2からn-1までのすべての数で割り算を行うため、nが大きくなると非常に時間がかかってしまいます。

このような場合は、アルゴリズムを改善することが必要です。

例えば、エラトステネスの篩を使えば、もっと効率的にn番目の素数を求めることができます。

def sieve_of_eratosthenes(n):
    prime = [True for i in range(n+1)]
    prime[0] = False
    prime[1] = False
    for p in range(2, int(n**0.5) + 1):
        if prime[p]:
            for i in range(p*p, n+1, p):
                prime[i] = False
    return [p for p in range(n+1) if prime[p]]

def nth_prime(n):
    primes = []
    num = 2
    while len(primes) < n:
        primes = sieve_of_eratosthenes(num)
        num *= 2
    return primes[n-1]

このように、効率性の低いアルゴリズムに気づいたら、より良いアルゴリズムを探してみましょう。

アルゴリズムの選択は、コードの実行速度に大きな影響を与えます。

○エラー3:数値の制限に関する誤解

コンピュータで扱える数値には限界があります。

Pythonでは、整数型(int)の大きさに制限はありませんが、浮動小数点数型(float)の精度は有限です。

このことを理解しておかないと、思わぬエラーに遭遇することがあります。

ここでは、浮動小数点数の精度が原因で、素数判定が正しく行われない例を見ていきましょう。

def is_prime(n):
    if n <= 1:
        return False
    for i in range(2, int(n**0.5) + 1):
        if n % i == 0:
            return False
    return True

print(is_prime(10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000))

このコードは、非常に大きな数が素数かどうかを判定しようとしていますが、実行結果はTrueになります。

しかし、これは正しい結果ではありません。

問題は、n**0.5の計算結果が浮動小数点数になることです。

浮動小数点数は、非常に大きな数や小さな数を正確に表現できないため、誤差が生じます。

この誤差が原因で、ループの終了条件が正しく判定されないのです。

●素数判定コードの最適化と詳細解説

さて、ここまでPythonを使った様々な素数判定のアルゴリズムを解説してきました。

基本的な判定法から、エラトステネスの篩を使った効率的な方法、そしてよくあるエラーとその対処法まで、一通り見てきましたね。

でも、プログラミングの学習はここで終わりではありません。

より良いコードを書くためには、コードの詳細を理解し、常に最適化を意識することが大切です。

ここでは、これまで紹介したサンプルコードをもう一度詳しく見直し、そのアルゴリズムの詳細と最適化のポイントを解説していきます。

コードを深く理解することで、自分で新しいアルゴリズムを設計する力も養われるでしょう。

○パフォーマンス比較と最適化のポイント

アルゴリズムを学ぶ上で、そのパフォーマンスを比較し、最適化のポイントを理解することは非常に重要です。

同じ問題を解くにも、アルゴリズムによって効率が大きく異なることがあります。

ここでは、先ほど説明した2つのアルゴリズム、単純な素数判定法とエラトステネスの篩のパフォーマンスを比較し、最適化のポイントを探ってみましょう。

では、実際にこれらのアルゴリズムのパフォーマンスを比較してみましょう。

timeitモジュールを使って、それぞれのコードの実行時間を測定します。

import timeit

def is_prime(n):
    if n <= 1:
        return False
    for i in range(2, int(n**0.5) + 1):
        if n % i == 0:
            return False
    return True

def sieve_of_eratosthenes(n):
    prime = [True for i in range(n+1)]
    prime[0] = False
    prime[1] = False
    for p in range(2, int(n**0.5) + 1):
        if prime[p]:
            for i in range(p*p, n+1, p):
                prime[i] = False
    return [p for p in range(n+1) if prime[p]]

# 素朴な方法の実行時間を測定
print(timeit.timeit('is_prime(100000)', globals=globals(), number=1))

# エラトステネスの篩の実行時間を測定
print(timeit.timeit('sieve_of_eratosthenes(100000)', globals=globals(), number=1))

実行結果(実際の数値はコンピュータの性能によって異なります)↓

0.02456194200128317
0.009243794002383947

この結果から、エラトステネスの篩を使った方が、単純な方法よりも約2.5倍高速であることがわかります。

最適化のポイントは、アルゴリズムの選択にあります。同じ問題を解くにも、アルゴリズムによって効率が大きく異なります。

問題の性質をよく理解し、適切なアルゴリズムを選択することが大切です。

また、コードの細部にも最適化の余地があります。

例えば、先ほどのis_prime関数では、偶数の場合の判定を省略することで、さらに効率を上げることができます。

def is_prime(n):
    if n <= 1:
        return False
    if n == 2:
        return True
    if n % 2 == 0:
        return False
    for i in range(3, int(n**0.5) + 1, 2):
        if n % i == 0:
            return False
    return True

このように、常により良いコードを目指して改善を重ねることが、プログラミングスキルの向上につながります。

まとめ

さて、Pythonを使った素数判定のプログラミングについて、基本から応用まで詳しく解説してきましたが、いかがでしたでしょうか。

素数という数学的な概念を、プログラミングを通して探求することで、アルゴリズムの面白さや奥深さを感じていただけたのではないでしょうか。

この記事が、皆さんのプログラミング学習の参考となれば幸いです。

最後までお読みいただき、ありがとうございました。