简体   繁体   中英

Prime numbers in python using generators

I am trying to write a program in python to generate prime numbers using logic similar to the one used in writing the prime number program in Haskell(the one that is found at the top of their website). I have written the following two generator functions.

def integers():
    i=2
    while True:
        yield i
        i+=1

def primes(li):
    x=li
    while True:
        i=next(x)
        y=(j for j in x if %i!=0)
        yield i
        x=y

Integers is a generator of the numbers 2,3,4,5... So my guess was

a=primes(integers())

should give a generator for the prime numbers but it gives nothing of the sort. This is the output I am getting.

>>> next(a)
2
>>> next(a)
3
>>> next(a)
4
>>> next(a)
5
>>> next(a)
6

Any suggestions?

Well, You wouldn't use this approach in Python where you generally avoid recursion. But here is a somewhat equivalent implementation, with a little help from itertools :

>>> from itertools import count, chain
>>> def filter_primes(integers):
...     p = next(integers)
...     yield from chain([p], filter_primes(x for x in integers if x%p !=0))
...
>>> primes = filter_primes(count(2))
>>> next(primes)
2
>>> next(primes)
3
>>> next(primes)
5
>>> next(primes)
7
>>> next(primes)
11
>>> next(primes)
13
>>> next(primes)
17
>>> next(primes)
19
>>> next(primes)
23
>>> next(primes)
29

Using more itertools help to take the first 20:

>>> primes = filter_primes(count(2))
>>> list(islice(primes, 20))
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71]
>>>

Your code looks reasonable. It does not work because it makes an assumption which surprisingly happens to be wrong: that a generator expression closes over the values it uses.

Instead, (j for j in x if j % i != 0) does not close over i ! This is why changing i later in the course of execution changes the filtering expression. Instead of building a bunch of filters against successive primes, the whole chain of generators mutates at every step to filter for the last generated value.

A demo:

>>> v = 1
>>> gen = (x for x in itertools.count() if x % v == 0)
>>> print next(gen), next(gen), next(gen)
0 1 2
>>> v = 3
>>> print next(gen), next(gen), next(gen)
3 6 9

A recursive solution is an obvious way to fix it.

Another way is to use a lambda that does the closure (cf IIFE ):

def primes(source):
    while True:
        value = next(source)
        yield value
        source = (lambda v: (j for j in source if j % v != 0))(value)

This works, but it incurs about the same amount of overhead as the straightforward recursion, and reads worse. It does not spend the call stack, though.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM