16

私はPythonのコツをつかみ始めているので、projecteuler.netのいくつかの問題について新しく習得したPythonスキルをテストし始めています。

とにかく、ある時点で、数'n'までのすべての素数のリストを取得する関数を作成することになりました。

関数のatmは次のようになります。

def primes(n):
    """Returns list of all the primes up until the number n."""

    # Gather all potential primes in a list.
    primes = range(2, n + 1)
    # The first potential prime in the list should be two.
    assert primes[0] == 2
    # The last potential prime in the list should be n.
    assert primes[-1] == n

    # 'p' will be the index of the current confirmed prime.
    p = 0
    # As long as 'p' is within the bounds of the list:
    while p < len(primes):
        # Set the candidate index 'c' to start right after 'p'.
        c = p + 1
        # As long as 'c' is within the bounds of the list:
        while c < len(primes):
            # Check if the candidate is divisible by the prime.
            if(primes[c] % primes[p] == 0):
                # If it is, it isn't a prime, and should be removed.
                primes.pop(c)
            # Move on to the next candidate and redo the process.
            c = c + 1
        # The next integer in the list should now be a prime, 
        # since it is not divisible by any of the primes before it. 
        # Thus we can move on to the next prime and redo the process.
        p = p + 1       
    # The list should now only contain primes, and can thus be returned.
    return primes

気になることが1つありますが、問題なく動作しているようです。コードにコメントしている間、この部分は突然外れたように見えました:

# Check if the candidate is divisible by the prime.
if(primes[c] % primes[p] == 0):
    # If it is, it isn't a prime, and should be removed from the list.
    primes.pop(c)
# Move on to the next candidate and redo the process.
c += 1

候補が素数で割り切れない場合は、「c+1」にある次の候補を調べます。問題ありません。

ただし、候補が素数で割り切れる場合は、最初にそれをポップしてから、「c+1」にある次の候補を調べます。ポップした後の次の候補は「c+1」ではなく「c」にあることに気づきました。「c」でポップした後、次の候補はそのインデックスに「分類」されるからです。

次に、ブロックは次のようになるはずだと思いました。

# If the candidate is divisible by the prime:
if(primes[c] % primes[p] == 0):
    # If it is, it isn't a prime, and should be removed from the list.
    primes.pop(c)
# If not:
else:
    # Move on to the next candidate.
    c += 1

上記のブロックは私にはもっと正しいように見えますが、なぜ元の作品がどうやらうまく機能したのか疑問に思います。

だから、ここに私の質問があります:

素数ではないことが判明した候補をポップした後、私の元のコードのように、次の候補は同じ素数で割り切れないと仮定できますか?

もしそうなら、それはなぜですか?

提案された「安全な」コードは、「安全でない」コードでスキップされた候補に対して不必要なチェックを行うだけでしょうか?

PS:

上記の仮定をアサーションとして「unsafe」関数に書き込んでみて、n=100000でテストしました。問題は発生しませんでした。変更されたブロックは次のとおりです。

# If the candidate is divisible by the prime:
if(primes[c] % primes[p] == 0):
    # If it is, it isn't a prime, and should be removed.
    primes.pop(c)
    # If c is still within the bounds of the list:
    if c < len(primes):
        # We assume that the new candidate at 'c' is not divisible by the prime.
        assert primes[c] % primes[p] != 0
# Move on to the next candidate and redo the process.
c = c + 1
4

6 に答える 6

11

はるかに大きな数では失敗します。最初の素数は71です。これは、候補者が失敗する可能性があるためです。71の最小の失敗候補は10986448536829734695346889であり、これは番号10986448536829734695346889+142を覆い隠します。

def primes(n, skip_range=None):
    """Modified "primes" with the original assertion from P.S. of the question.
    with skipping of an unimportant huge range.
    >>> primes(71)
    [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71]
    >>> # The smallest failing number for the first failing prime 71:
    >>> big_n = 10986448536829734695346889
    >>> primes(big_n + 2 * 71, (72, big_n))
    Traceback (most recent call last):
    AssertionError
    """
    if not skip_range:
        primes = list(range(2, n + 1))
    else:
        primes = list(range(2, skip_range[0]))
        primes.extend(range(skip_range[1], n + 1))
    p = 0
    while p < len(primes):
        c = p + 1
        while c < len(primes):
            if(primes[c] % primes[p] == 0):
                primes.pop(c)
                if c < len(primes):
                    assert primes[c] % primes[p] != 0
            c = c + 1
        p = p + 1
    return primes

# Verify that it can fail.
aprime = 71   # the first problematic prime 
FIRST_BAD_NUMBERS = (
        10986448536829734695346889, 11078434793489708690791399,
        12367063025234804812185529, 20329913969650068499781719,
        30697401499184410328653969, 35961932865481861481238649,
        40008133490686471804514089, 41414505712084173826517629,
        49440212368558553144898949, 52201441345368693378576229)

for bad_number in FIRST_BAD_NUMBERS:
    try:
        primes(bad_number + 2 * aprime, (aprime + 1, bad_number))
        raise Exception('The number {} should fail'.format(bad_number))
    except AssertionError:
        print('{} OK. It fails as is expected'.format(bad_number))

私はこれらの数をパズルのような複雑なアルゴリズムで解き、n個のモジュロ小素数の可能な余りを検索しました。最後の簡単なステップは、完全なnを取得することでした(Pythonコードの3行の中国剰余定理による)。素数階乗(71)よりも小さい120の基本解すべて=2 * 3 * 5 * 7 * 11 * 13 * 17 * 19 * 23 * 29 * 31 * 37 * 41 * 43 * 47 * 53 * 59 * 61 * 67 * 71この数のすべての倍数で定期的に繰り返されることを私は知っています。テストした素数の10年ごとにアルゴリズムを何度も書き直しました。これは、10年ごとのソリューションが、以前よりもはるかに遅いためです。たぶん、許容時間内に素数73または79に対して同じアルゴリズムを使用したより小さな解を見つけます。


編集:

安全でない元の機能の完全なサイレント失敗も見つけたいと思います。たぶん、異なる素数から構成されるいくつかの候補が存在します。この解決方法は、最終的な結果を後で延期するだけです。すべてのステップは、時間とリソースのためにますます高価になります。したがって、1つまたは2つの素数で構成される数だけが魅力的です。

隠された候補cが適切なのは2つの解だけだと思います。c = p ** nまたは、c = p1 * p ** nまたはpp1が素数で、nが1より大きい累乗である場合、 pよりも小さい素数で割り切れない場合、およびc- 2ncは、 pよりも小さい素数で割り切れます。バリアントp1*p ** nは、同じcがp1p1 <p )に対して以前に失敗したことも必要とします。これは、そのような候補の数が無限であることをすでに知っているためです。c = p1 ** n1 * p ** nc - 2 * p

編集:失敗の小さな例を見つけました:プライム79の番号121093190175715194562061。(71の約90分の1です)すべての702612基本ソリューションが30以上かかったため、同じアルゴリズムで小さな例を見つけることはできません。私のラップトップのプライム79の時間。

また、400000000(4E10)未満のすべての候補と、関連するすべての素数について、質問のアサーションに失敗する候補がないことを確認しました。テラバイトのメモリと数千年の時間が経過するまで、時間計算量はO((n / log(n))^ 2)または非常に類似しているため、アルゴリズムのアサーションは通過します。

于 2012-12-10T23:53:40.987 に答える
4

あなたの観察は正確であるように思われます、それはかなり良いキャッチです。

少なくともいくつかの場合、それが機能する理由は、合成数が実際には複数の素数に因数分解されているためだと思います。したがって、内側のループは最初の要素の値を見逃す可能性がありますが、その後の要素で値を取得します。

小さな「n」の場合は、リストの値を出力して、これが発生しているかどうかを確認できます。

ちなみに、素数を見つけるこの方法は、Eratothenesのふるいに基づいています。ふるい分けを行う場合、「c」が「p」の倍数である場合、次の値が同じ素数の倍数になることはありません。

問題は、p *xとp*(x + 1)の間のすべての値がpとp * x+1よりも小さい素数で割り切れる場合があるかどうかです。(これは、アルゴリズムが値を見逃し、後でキャッチされない場所です。)ただし、これらの値の1つは偶数であるため、ラウンド「2」で削除されます。したがって、本当の問題は、p *xとp*(x + 2)の間のすべての値がp未満の数値で割り切れる場合があるかどうかです。

一方で、この条件を満たす100未満の数字は考えられません。p = 5の場合、5の2つの連続する倍数の間に2または3で割り切れない値が常にあります。

素数の間隔とシーケンスについては多くのことが書かれているようですが、p未満の数で割り切れる連続した整数のシーケンスについてはそれほど多くはありません。試行錯誤の末、39,474(17 * 2,322)から39,491(17 * 2,233)までのすべての数値は17未満の整数で割り切れると判断しました。

39,475  5
39,476  2
39,477  3
39,478  2
39,479  11
39,480  2
39,481  13
39,482  2
39,483  3
39,484  2
39,485  5
39,486  2
39,487  7
39,488  2
39,489  3
39,490  2

これが最初のそのような値であるかどうかはわかりません。ただし、これの2倍の長さのシーケンスを見つける必要があります。それはありそうもないと思いますが、証拠があるかどうかはわかりません。

私の結論は、元のコードは機能するかもしれないが、あなたの修正は正しいことであるということです。そのようなシーケンスがないという証拠がなければ、それはバグのように見えますが、非常に、非常に、非常にまれなバグである可能性があります。

于 2012-12-06T17:07:16.040 に答える
1
  • nとmが最後の除数pで割り切れないように、可能な素数の連続シーケンスで2つの数n、mが与えられると、m --n <p
  • q(次に高い除数)> pが与えられ、nがqで割り切れる場合、qで割り切れる次の数はn + q> n + p> mであるため、除算テストの現在の反復ではmをスキップする必要があります

    Here n = primes[c] 
    
    m = primes[c + 1], i.e. primes[c] after primes.pop(c)
    
    p = primes[p]
    q = primes[p+1] 
    
于 2012-12-06T17:51:05.183 に答える
0

ここにアイデアがあります:

トリプティクは1を説明し、cの次の数はc + pになることはできませんが、それでもc+2pになることはないことを示す必要があります。

素数=[2]を使用する場合、2で割り切れる数である「非素数」を1つだけ連続して持つことができます。

素数=[2,3]を使用すると、3つの連続する「非素数」、2で割った数、3で割った数、2で割った数を作成でき、次の数を取得できません。または

2,3,4=>3つの連続した「非素数」

2と3は「非素数」ではありませんが、これらの数の観点から考える方が簡単です。

[2,3,5]を使用すると、次のようになります。

2,3,4,5,6=>5つの連続した「非素数」

[2,3,5,7]を使用すると、次のようになります。

2,3,4,5,6,7,8,9,10=>9つの連続した「非素数」

パターンが現れます。私たちが得ることができる最も連続した非素数は次の素数-2です。

したがって、next_prime <p * 2 + 1の場合、素数がまだ与えられているため、連続する非素数の数が十分に長くないため、cとc+2pの間に少なくともいくつかの数が必要です。

非常に大きな数についてはわかりませんが、これnext_prime < p * 2 + 1は非常に大きな数になる可能性が高いと思います。

これが理にかなっていて、いくらかの光を加えることを願っています。


1 トリプティクの答えは削除されました。

于 2012-12-06T19:06:35.007 に答える
0

これはリモートで決定的な答えを提供しませんが、これで私が試したことは次のとおりです。

ここで必要な仮定を次のように言い換えました(lpfは最小素因数を表します):

For any composite number, x, where:
    lpf(x) = n
There exists a value, m, where 0 < m < 2n and:
    lpf(x+m) > n

不等式を満たすために合成数(x + m)が存在しないxの値が存在することは簡単に証明できます。二乗プライムは次のことを示しています。

lpf(x) = x^.5, so x = n^2
n^2 + 2n < (n + 1)^2 = n^2 + 2n + 1

したがって、任意の2乗素数の場合、これが当てはまるには、x <p <x+2nの範囲に存在する素数pが存在する必要があります。

素数定理(素数の漸近分布約x /(ln x))と比較した平方の漸近分布(x ^ .5)を考えると、これは結論付けることができると思いますが、実際には、素数の私の理解は定理はせいぜい限られています。

そして、私にはその結論を非二乗合成数に拡張するための戦略がまったくないので、それは有用な手段ではないかもしれません。

上記の問題の言い換えを使用して、プログラムテスト値をまとめました。

このステートメントを直接テストすると、述べられているようにアルゴリズムを実行するだけで、幸運な結果がすべて削除されます。幸運な結果とは、スキップされた値が安全ではない可能性があることを指しますが、スキップされた値が現在繰り返されている数値で割り切れない、または後続の反復によって取得されます。基本的に、アルゴリズムが正しい結果を取得したが、削除された各値の最小素因数が見つからないか、各素因数を厳密にチェックしていない場合、私はそれに満足していません。そのようなケースが存在する場合、それが幸運にならず(そうであるかもしれないが珍しい)、誤った結果をもたらすケースも存在すると仮定するのは合理的だと思います。

ただし、私のテストを実行すると、2〜2,000,000の値に反例は表示されません。したがって、その価値については、私のロジックが正しくない場合を除いて、述べたアルゴリズムの値は少なくとも2,000,000まで安全である必要があります。

それが私が追加しなければならないことです。素晴らしい質問、Phazyck、それを楽しんだ!

于 2012-12-06T22:59:47.063 に答える
-2

素数pが候補cを除算する場合、pで割り切れる次に大きい候補はc+pです。したがって、元のコードは正しいです。

ただし、これは素数のリストを作成するための腐った方法です。n = 1000000で試して、どれだけ遅くなるかを確認してください。問題は、ふるいを使用する必要があるときに試行除算を実行していることです。これが簡単なふるいです(擬似コード、Pythonまたは他の言語への翻訳をさせます):

function primes(n)
    sieve := makeArray(2..n, True)
    for p from 2 to n step 1
        if sieve[p]
            output p
            for i from p+p to n step p
                sieve[i] := False

これにより、1秒未満で100万未満の素数が得られるはずです。そして、さらに高速な他のふるいアルゴリズムがあります。

このアルゴリズムはエラトステネスのふるいと呼ばれ、約2200年前にギリシャの数学者によって発明されました。エラトステネスは興味深い仲間でした。彼は、素数をふるいにかけるだけでなく、飛躍の日と緯度と経度のシステムを発明し、太陽から地球までの距離と地球の周囲を正確に計算し、しばらくの間、プトレマイオス図書館の主任司書でした。アレクサンドリアで。

素数を使ったプログラミングについてもっと学ぶ準備ができたら、ブログでこのエッセイを控えめにお勧めします。

于 2012-12-06T17:01:05.733 に答える