web-dev-qa-db-ja.com

Pythonで素数の効率的な無限ジェネレーターを実装する方法は?

これは宿題ではありません、私は興味があります。

ここではINFINITEがキーワードです。

for p in primes()として使いたいです。これはHaskellの組み込み関数だと思います。

したがって、答えは「Just do a Sieve」ほど単純ではありません。

まず第一に、あなたは連続する素数がいくつ消費されるかわかりません。さて、一度に100個を作り出すことができるとしましょう。同じSieveアプローチと素数式の頻度を使用しますか?

私は非並行アプローチを好みます。

読んで(そして書いてくれてありがとう;))!

60
Hamish Grubijan

OPはefficientの実装を要求するので、David Eppstein/Alex Martelliが active state 2002 code を大幅に改善しています(ここで- 彼の答え ):候補に正方形が表示されるまで、素数の情報を辞書に記録しないでください。生成されたn個の素数に対して、スペースの複雑さをO(n)ではなくO(sqrt(n))に下げます(- π(sqrt(n log n))2 sqrt(n log n)/ log(n log n)2 sqrt(n/log n))。その結果、時間の複雑さも改善されます。つまり、 それはより速く実行されます

「スライディングシーブ」を各基本素数の現在の倍数のディクショナリとして(つまり、現在のプロダクションポイントの平方根より下に)、それらのstep値とともに作成します。

from itertools import count
                                         # ideone.com/aVndFM
def postponed_sieve():                   # postponed sieve, by Will Ness      
    yield 2; yield 3; yield 5; yield 7;  # original code David Eppstein, 
    sieve = {}                           #   Alex Martelli, ActiveState Recipe 2002
    ps = postponed_sieve()               # a separate base Primes Supply:
    p = next(ps) and next(ps)            # (3) a Prime to add to dict
    q = p*p                              # (9) its sQuare 
    for c in count(9,2):                 # the Candidate
        if c in sieve:               # c's a multiple of some base prime
            s = sieve.pop(c)         #     i.e. a composite ; or
        Elif c < q:  
             yield c                 # a prime
             continue              
        else:   # (c==q):            # or the next base prime's square:
            s=count(q+2*p,2*p)       #    (9+6, by 6 : 15,21,27,33,...)
            p=next(ps)               #    (5)
            q=p*p                    #    (25)
        for m in s:                  # the next multiple 
            if m not in sieve:       # no duplicates
                break
        sieve[m] = s                 # original test entry: ideone.com/WFv4f

(ここの古い元のコードは、以下の the answer によって Tim Peters で見られるように変更を組み込むように編集されています)。関連する議論については this も参照してください。

同様 2-3-5-7ホイール ベースのコード 実行2.15倍高速 (これは3/2 * 5/4 * 7/6 = 2.1875の理論的改善に非常に近い)です。

66
Will Ness

後世のために、ここにPython 3のWill Nessの美しいアルゴリズムを書き直します。いくつかの変更が必要です(イテレータには.next()メソッドがなくなりましたが、新しいnext()組み込み関数)その他の変更はおもしろいものです(新しい_yield from <iterable>_を使用すると、元の4つのyieldステートメントが置き換えられます。もっと読みやすくするためです(私は使いすぎのファンではありません;-) 1文字の変数名)。

オリジナルよりも大幅に高速ですが、アルゴリズム上の理由ではありません。スピードアップのほとんどは、元のadd()関数を削除し、代わりにインラインで実行したためです。

_def psieve():
    import itertools
    yield from (2, 3, 5, 7)
    D = {}
    ps = psieve()
    next(ps)
    p = next(ps)
    assert p == 3
    psq = p*p
    for i in itertools.count(9, 2):
        if i in D:      # composite
            step = D.pop(i)
        Elif i < psq:   # prime
            yield i
            continue
        else:           # composite, = p*p
            assert i == psq
            step = 2*p
            p = next(ps)
            psq = p*p
        i += step
        while i in D:
            i += step
        D[i] = step
_
38
Tim Peters

これはもともと私のコードではありませんが、投稿する価値はあります。オリジナルはここにあります: http://code.activestate.com/recipes/117119/

def gen_primes():
  D = {}
  q = 2  # first integer to test for primality.

  while True:
    if q not in D:
      # not marked composite, must be prime  
      yield q 

      #first multiple of q not already marked
      D[q * q] = [q] 
    else:
      for p in D[q]:
        D.setdefault(p + q, []).append(p)
      # no longer need D[q], free memory
      del D[q]

    q += 1

それはジェネレーターなので、他のように使用します。

primes = gen_primes()
for p in primes:
  print p

デスクトップで100万の素数を生成してセットに入れるには、1.62秒かかります。

5

segmented sieveを実行します。セグメントのサイズは、使用可能なメモリまたはビットセットの最大サイズによって決まります。

各セグメントについて、ある間隔[n; n + segment_size)をビットセットとして設定し、すべての素数を上限の平方根より下にふるいにかけます。

密な数のセットを扱うため、ビットセットを使用すると、ハッシュテーブルやツリーデータ構造よりもメモリの使用量が少なくなります。

5
starblue

それを行う別の方法:

import itertools
def primeseq():
    prime = [2]
    num = 0
    yield 2
    for i in itertools.count(3, 2):
        is_prime = True
        for num in prime:
            if i % num == 0:
                is_prime = False
                break
            Elif num ** 2 > i: 
                break
        if is_prime:
            prime.append(i)
            yield i
2
quantum

そして別の答えは、ここのerat3の答えよりもメモリ効率が良い:

import heapq

def heapprimegen():
    hp= []
    yield 2
    yield 3
    cn= 3
    nn, inc= 3, 6
    while 1:
        while cn < nn:
            yield cn
            heapq.heappush(hp, (3*cn, 2*cn))
            cn+= 2
        cn= nn+2
        nn, inc= heapq.heappushpop(hp, (nn+inc, inc))

辞書ではなく素数のヒープ(リスト)を維持します。明らかに速度が落ちます。

2
tzot

これは複雑なヒープベースの実装です。これは他のヒープベースの実装ほど高速ではありませんが(私の別の回答の速度の比較を参照)、使用するメモリははるかに少なくなります。

この実装では、同じ数の要素を含む2つのヒープ(tuとwv)を使用します。各要素はintペアです。 _q**2_(qは素数)までのすべての素数を見つけるために、各ヒープには最大で2*pi(q-1)要素が含まれます。ここで、pi(x)x以下の正の素数の数。したがって、整数の総数はせいぜい4*pi(floor(sqrt(n)))です。 (半分の量をヒープにプッシュすることにより、メモリ上で2の因数を得ることができますが、これによりアルゴリズムが遅くなります。)

上記の他のdictおよびヒープベースのアプローチ(たとえば、erat2b、heap_prime_gen_squaresおよびheapprimegen)は、約2 * pi(n)の整数を格納します。これは、それらが素数を見つけるたびにヒープまたはdictを拡張するためです。比較として:1_000_000素数を見つけるために、この実装は4141未満の整数を格納し、他の実装は1_000_000整数より多くを格納します。

_import heapq

def heap_prime_gen_smallmem():
    yield 2
    yield 3
    f = 5
    fmar3 = 2
    q = 7
    q6 = 7 * 6
    qmar3 = 4
    tu = [(25, 30), (35, 30)]
    vw = [(25, 30), (35, 30)]
    while True:
        qmar3 += 2   
        if qmar3 == 6:  
            qb = q + 4
            q6b = q6 + 24
            qmar3 = 2
        else:
            qb = q + 2
            q6b = q6 + 12
        if q < tu[0][0]:
            d = q * q
            while f < d:
                a, b = vw[0]
                if f < a: 
                    yield f   
                else:
                    a, b = vw[0]
                    heapq.heapreplace(vw, (a + b, b))
                    a, b = vw[0]
                    while f >= a:
                        heapq.heapreplace(vw, (a + b, b))
                        a, b = vw[0]   
                fmar3 += 2
                if fmar3 == 6:
                    f += 4
                    fmar3 = 2
                else:
                    f += 2
            c = q * qb   
            heapq.heappush(tu, (d, q6))
            heapq.heappush(tu, (c, q6))
            heapq.heappush(vw, (d, q6))
            heapq.heappush(vw, (c, q6))
        else:
            a, b = tu[0]
            heapq.heapreplace(tu, (a + b, b))
            a, b = tu[0]  
            while q >= a:
                heapq.heapreplace(tu, (a + b, b))
                a, b = tu[0]
        q = qb
        q6 = q6b
_
1
pts

これはシンプルですが、dictの代わりにヒープを使用したもので、ひどく遅くはありません。

import heapq

def heap_prime_gen_squares(): 
    yield 2  
    yield 3  
    h = [(9, 6)]
    n = 5
    while True:
        a, b = h[0]
        while n < a:
            yield n
            heapq.heappush(h, (n * n, n << 1))
            n += 2
        heapq.heapreplace(h, (a + b, b))  # Replace h[0], which is still (a, b).

最初の100万素数のユーザー時間の速度測定(数値が小さいほど良い):

  • postponed_sieve(dictベース):8.553s
  • erat2b(dictベース):9.513s
  • erat2a(dictベース):10.313s
  • heap_prime_gen_smallmem(ヒープベース):23.935s
  • heap_prime_gen_squares(ヒープベース):27.302s
  • heapprimegen(dictベース):145.029秒

したがって、dictベースのアプローチが最速のようです。

1
pts

これは、Python2で記述された非常に高速な無限ジェネレーターですが、Python3に簡単に調整できます。これを使用して10 ** 9までの素数を追加するには、以下を使用します。

from itertools import takewhile
from functools import partial
from operator import gt
print (sum(takewhile(partial(gt, 10**9), prime_gen_inf())))

これはセグメント化されたふるいであり、Will Nessのアルゴリズムより高速ですが明らかにエレガントではありません。

from operator import mul
from functools import reduce
def prod(x): return reduce(mul, x, 1)


def build_sieve(wheel):
    w = prod(wheel)
    w_phi = prod([p-1 for p in wheel])
    rems = [a for a in range(w) if all(a % p for p in wheel)]
    assert len(rems) == w_phi
    inv = {a:pow(a, w_phi - 1, w) for a in rems}
    try:
        known_p = wheel + rems[1 : rems.index(rems[1]*rems[1])]
    except ValueError:
        known_p = wheel + rems[1:]
    return wheel, w, w_phi, rems, inv, known_p

#Adjust the chunk variable based on your computer's architecture.
#
#Adjust the line with #! if you don't need "true" infinite.  If you don't need
#primes larger than 1<<32, use array('H', []), if 1<<64 use 'L', if 1<<128 (in
#Python3) use 'Q', otherwise use empty list [].
#To save memory, comment out the lines with #*, and uncomment the commented-out
#lines 
import itertools
from itertools import islice, count, compress, izip
chain_f = itertools.chain.from_iterable
from array import array
def prime_gen_inf(chunk=250000, sieve_info = build_sieve([2,3,5,7])):
    """    Indefinitely yields primes    """
    wheel, w, w_phi, rems, inv, known_p = sieve_info
    for p in known_p: yield p
    new_n = 0;
    while True:
        size = min(chunk, (p * p - new_n) / w)
        sieve = bytearray([1]) * size * w_phi
        n, new_n = new_n, new_n + size * w
        if not n:
            zero = bytearray([0])
            seen = len(known_p) - len(wheel) + 1
            sieve[:seen:1] = zero * seen
            p_gen = islice(prime_gen_inf(), len(wheel), None)
            new_p = next(p_gen)
            ps = []                                         #! array('H', [])
            p_invs = bytearray([])                                         #*
        while new_p * new_p < new_n:
            ps.append(new_p)
            p_invs.append(inv[new_p % w])                                  #*
            new_p = next(p_gen)
        for p, p_inv, modp in izip(ps, p_invs, [-n % p for p in ps]):      #*
            s = [(modp + p * (p_inv * (r - modp) % w)) / w for r in rems]  #*
        #for p in ps:
        #    s = [(-n%p + p * (inv[p%w] * (r - -n%p) % w)) / w for r in rems]
            for i, start in enumerate(s):
                slice_size = ((size - start - 1) / p + 1)
                sieve[i + start * w_phi :: p * w_phi] = zero * slice_size
        for p in compress(chain_f(izip(*[count(n+r, w) for r in rems])), sieve):
            yield p
1
Jason

Haskellでの実行方法に少し忠実なジェネレーターを以下に示します。既知の素数のコンポジットに対してフィルタリングし、残りの素数をリストに追加します。

def gen_primes():
    primes = []
    i = 2
    while True:
        prime = True
        for p in primes:
            if not (i % p):
                prime = False
                break
        if prime:
            yield i
            primes.append(i)
        i += 1
1
avpx

私は数回前に無限素数ジェネレータについての記事を書きました:

http://stacktrace.it/2008/01/progetto-eulero-problema-3/

それはイタリア語ですが、Googleを使用して厄介な翻訳があるかもしれません: http://tinyurl.com/yzpyeom

1
piro

私は投稿が古いことを知っていますが、私はこの質問に出くわしました...次のコードは非常に単純なアイデアに基づいています:エラトステネスのふるいを増やすことです。この解決策は、ここにある最良の解決策よりも本当に遅いですが、理解しやすく、読みやすいように設計されています...

ふるいの結果を格納するために整数を使用しました。バイナリ形式では、整数は_0_ sおよび_1_ s、_0_のリストで、iiが素数でない場合、_1_は素数である可能性があります。必要な無限大は、Python 3つの整数が無限であるという事実の結果です。

_def primes():
    container, size = 1 << 2, 3 # we start with 0b100 (from right to left: 0 and 1 are not primes, 2 is
    last_prime = 1
    while True:
        prime = next((j for j in range(last_prime+1, size) if container & 1 << j), None) # find the next prime
        while not prime:
            container, size = expand(container, size, 2**16) # add 65536 cells and sieve the container
            prime = next((j for j in range(last_prime+1, size) if container & 1 << j), None)
        yield prime
    last_prime = prime
_

コンテナを拡張するには?コンテナーの左側に(バイナリ形式で)一連の_1_ sを追加し、ふるいにかけるだけです。これは、標準的なふるいと同じですが、若干の違いがあります。標準のふるいで、素数のiを見つけた場合、_i*i_でセルを交差し始め、iのステップが追加されます。

ここでは、これはコンテナの最初の部分に対して行われた可能性があります。 _i*i_よりも遠い場合は、コンテナーの新しい部分の最初から開始する必要があります。

_def expand(container, size, n):
    new_size = size + n
    container += (1 << (new_size + 1) - 1) - (1 << size) # add n 1's
    for i in range(2, new_size):
        if container & (1 << i): # i is a prime
            t = sum(1 << j for j in range(max(i, size // i)*i, new_size, i)) # set 1 for all mutiple
            container &= ~t # cross the cells

    return container, new_size
_

100万の素数をテストします。

_import itertools
assert 78498 == len(list(itertools.takewhile(lambda p: p<1000000, primes())))
_
0
jferard