Python Forum
prime numbers with iterator and generator
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
prime numbers with iterator and generator
#1
Hello.

Let's make an iterator like this:

def integers_starting_from(n):
    while True:
        yield n
        n += 1
This makes an infinite sequence of integers.

for i in integers_starting_from(0):
    if i > 100:
        break
    else:
        print(i)

        
0
1
2
3
4
5
6
......
Next, let's make an iterator to produce prime numbers like this.

class Sieve(object):
    def __init__(self, stream):
        self.stream = stream
    def __iter__(self):
        return self
    def __next__(self):
        try:
            head = self.stream.__next__()
            self.stream = filter(lambda x: x % head != 0, self.stream)
            return head
        except StopIteration as e:
            raise e
Of course, this works too.

for prime in Sieve(integers_starting_from(2)):
    if prime > 100:
        break
    else:
        print(prime)

        
2
3
5
7
11
13
17
19
23
29
31
37
41
43
47
53
59
61
67
71
73
79
83
89
97
However the problem occurs here.
With the same logic stated above, let's try making a generator to produce prime numbers.
I wrote something like this:

def sieve(stream):
    while True:
        head = stream.__next__()
        yield head
        stream = filter(lambda x: x % head != 0, stream)
This does not work properly.
Did I make something wrong?

Thanx.
Reply
#2
filter has a list of functions that it uses to test the iterable. You expected that list to look like this:
Output:
lambda x: x % 2 != 0 lambda x: x % 3 != 0 lambda x: x % 5 != 0
And this is how the list looked in your iterator version.

In the generator version the list looked like this:
Output:
lambda x: x % head != 0 lambda x: x % head != 0 lambda x: x % head != 0
All the functions are the same. You only test against the last prime number, not all known primes. And since the test is faulty, every number looks like a prime since "x % (x-1) != 0)" is True for all values of x > 2.

To fix the problem change this:
stream = filter(lambda x: x % head != 0, stream)
to this:
stream = filter(lambda x, y=head: x % y != 0, stream)
Reply
#3
Note that integers_starting_from() already exists in the standard library, it is itertools.count().

Here is a less functionaly pure but nonetheless interesting sieve

from itertools import count
from functools import partial

def fil(s, x):
    if b := all(x % y for y in s):
        s.append(x)
    return b

def sieve():
    return filter(partial(fil, []), count(2))


if __name__ == '__main__':
    for x in sieve():
        print(x)
        if x > 100:
            break
Reply
#4
(Dec-12-2022, 02:51 PM)deanhystad Wrote: filter has a list of functions that it uses to test the iterable. You expected that list to look like this:
Output:
lambda x: x % 2 != 0 lambda x: x % 3 != 0 lambda x: x % 5 != 0
And this is how the list looked in your iterator version.

In the generator version the list looked like this:
Output:
lambda x: x % head != 0 lambda x: x % head != 0 lambda x: x % head != 0
All the functions are the same. You only test against the last prime number, not all known primes. And since the test is faulty, every number looks like a prime since "x % (x-1) != 0)" is True for all values of x > 2.

To fix the problem change this:
stream = filter(lambda x: x % head != 0, stream)
to this:
stream = filter(lambda x, y=head: x % y != 0, stream)

O.K, thank you very much!
This seems a sort of pit fall; thus I would watch out when I use a generator.
Reply
#5
(Dec-12-2022, 07:23 PM)Gribouillis Wrote: Note that integers_starting_from() already exists in the standard library, it is itertools.count().

Oops, I miss that one.
Thanx to notify me.

Quote:Here is a less functionaly pure but nonetheless interesting sieve

from itertools import count
from functools import partial

def fil(s, x):
    if b := all(x % y for y in s):
        s.append(x)
    return b

def sieve():
    return filter(partial(fil, []), count(2))


if __name__ == '__main__':
    for x in sieve():
        print(x)
        if x > 100:
            break

Interesting.
I'll check out the partial one, too.
Thank you!
Reply
#6
(Dec-13-2022, 06:06 PM)cametan_001 Wrote: This seems a sort of pit fall; thus I would watch out when I use a generator.
When you do this:
stream = filter(lambda x: x % head != 0, stream)
Python creates a closure that contains the variable "head" and the anonymous function generated by the lambda expression.
def anoinymous(x):
    return x % head != 0
When the lambda expression is in your iterator, the variable "head" is different for each closure because each call to __next__(self) creates a new "head" variable.

When the lambda expression is in your generator, the variable "head" is the same for each closure because you never leave the generator scope.

You should probably use this notation for your iterator to prevent having to create a closure for each lambda.
stream = filter(lambda x, y=head: x % y != 0, stream)
This creates an anonymous function that kind of looks like this:
def anonymous(x, y=2):
    return x % y != 0
No need now for a closure because we don't use any variables, only function arguments.
Reply
#7
(Dec-13-2022, 07:00 PM)deanhystad Wrote:
(Dec-13-2022, 06:06 PM)cametan_001 Wrote: This seems a sort of pit fall; thus I would watch out when I use a generator.
When you do this:
stream = filter(lambda x: x % head != 0, stream)
Python creates a closure that contains the variable "head" and the anonymous function generated by the lambda expression.
def anoinymous(x):
    return x % head != 0
When the lambda expression is in your iterator, the variable "head" is different for each closure because each call to __next__(self) creates a new "head" variable.

When the lambda expression is in your generator, the variable "head" is the same for each closure because you never leave the generator scope.

You should probably use this notation for your iterator to prevent having to create a closure for each lambda.
stream = filter(lambda x, y=head: x % y != 0, stream)
This creates an anonymous function that kind of looks like this:
def anonymous(x, y=2):
    return x % y != 0
No need now for a closure because we don't use any variables, only function arguments.

Thanks for the detailed explanation!
I tried some experiments like making a local function:

def anoinymous(x):
            nonlocal head
            return x % head != 0
, put it in the filter, and saw what is going on; however, as a result, what you showed is the only way to be succeess.

Anyway, thank you very much!
Reply
#8
Or use a partial function. In a partial function the argument values are evaluated when the partial function is created, unlike a lambda expression where the argument values are evaluated when the expression is executed.
Reply
#9
(Dec-15-2022, 04:29 PM)deanhystad Wrote: Or use a partial function. In a partial function the argument values are evaluated when the partial function is created, unlike a lambda expression where the argument values are evaluated when the expression is executed.

Hmm, like this.

def anoinymous(y, x):
    return x % y != 0

def sieve(stream):
    while True:
        head = stream.__next__()
        yield head
        stream = filter(partial(anoinymous, head), stream)
Yeah, it works.
Otherwise:

def sieve(stream):
    while True:
        head = stream.__next__()
        yield head
        stream = filter(partial(lambda y, x: x % y != 0, head), stream)
Interesting.
The order of arguments seems like functools.reduce, though.

Anyway, thanks!
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  prime numbers astral_travel 28 3,728 Nov-08-2022, 09:23 PM
Last Post: astral_travel
  resetting an iterator to full Skaperen 7 6,997 Feb-20-2022, 11:11 PM
Last Post: Skaperen
  popping an iterator Skaperen 11 3,718 Oct-03-2021, 05:08 PM
Last Post: Skaperen
  q re glob.iglob iterator and close jimr 2 2,241 Aug-23-2021, 10:14 PM
Last Post: perfringo
  Problem with an iterator grimm1111 9 4,331 Feb-06-2021, 09:22 PM
Last Post: grimm1111
  Multi-class iterator Pedroski55 2 2,394 Jan-02-2021, 12:29 AM
Last Post: Pedroski55
  Return prime numbers from range krzyfigh 2 1,933 Apr-20-2020, 08:08 PM
Last Post: krzyfigh
  Generator function for even numbers mp3909 4 6,025 Mar-21-2020, 07:40 PM
Last Post: buran
  is a str object a valid iterator? Skaperen 6 5,650 Jan-27-2020, 08:44 PM
Last Post: Skaperen
  Can i prevent the random generator to generate already used numbers? MauserMan 3 2,891 Jan-05-2020, 04:44 PM
Last Post: MauserMan

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020