How to implement an efficient infinite generator of prime numbers in Python?

后端 未结 13 2254
醉酒成梦
醉酒成梦 2020-11-22 01:50

This is not a homework, I am just curious.

INFINITE is the key word here.

I wish to use it as for p in primes(). I believe that this is a built-

相关标签:
13条回答
  • 2020-11-22 02:30

    I wrote an article about an infinite primes generator some times ago:

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

    It's in Italian but you may have a pesky translation using Google: http://tinyurl.com/yzpyeom

    0 讨论(0)
  • 2020-11-22 02:31

    Do a segmented sieve, where the size of a segment is determined by available memory or the maximal size of a bitset.

    For each segment represent the numbers in some interval [n; n + segment_size) as a bit set and sieve with all prime numbers below the square root of the upper bound.

    Using a bit set uses less memory than a hash table or tree data structure, because you are working with dense sets of numbers.

    0 讨论(0)
  • 2020-11-22 02:31

    Here's a pretty fast infinite generator, written in Python2 but easily adjusted to Python3. To use it to add the primes up to 10**9, use the following:

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

    It's a segmented sieve, faster but obviously less elegant than Will Ness's algorithm.

    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
    
    0 讨论(0)
  • 2020-11-22 02:33

    Here's a generator that's a little truer to how it's done in Haskell: filtering against composites of known primes, then adding the remaining primes to the list.

    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
    
    0 讨论(0)
  • 2020-11-22 02:36

    I know the post is old, but I came by myself across this question... The following code is based on a very simple idea: a growing sieve of Eratosthenes. This solution is really slower than the best ones here, but it is easy to grasp and designed to be readable...

    I used integers to store the results of the sieve. In binary format, an integer is a list of 0s and 1s, 0 at position i if i is not a prime, 1 if it may be a prime. The requisite infinity is a result of the fact that Python 3 integers are unbounded.

    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
    

    How to expand the container? Just add a bunch of 1s at the left of the container (in binary format) and sieve them. This is identical to the standard sieve, with a slight difference. In the standard sieve, if we find a prime i, we start to cross the cells at i*i, with a step of i.

    Here, this may have been done for the first part of container. We just need to start at the beginnig of the new part of the container if it is farther than 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
    

    Test for a million primes:

    import itertools
    assert 78498 == len(list(itertools.takewhile(lambda p: p<1000000, primes())))
    
    0 讨论(0)
  • 2020-11-22 02:37

    This isn't originally my code, however, it's worth posting. The original can be found here: 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
    

    It's a generator, so use it like any other.

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

    It takes 1.62s to generate and put into a set, 1 million primes, on my desktop.

    0 讨论(0)
提交回复
热议问题