1

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?

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • 3
    Possible duplicate of [Prime generator in python](https://stackoverflow.com/questions/34022992/prime-generator-in-python) – Prune Jun 20 '17 at 17:22
  • Why is your `integers` function generating multiples of two? You could've avoided this by incrementing `i` by 2 instead of 1 on each iteration. – ForceBru Jun 20 '17 at 17:39
  • @ForceBru I believe the intent is to reproduce the implementation found at the top of the page [here](https://www.haskell.org/) – juanpa.arrivillaga Jun 20 '17 at 17:42

2 Answers2

0

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]
>>>
juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172
0

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.

9000
  • 39,899
  • 9
  • 66
  • 104