Posts: 7
Threads: 2
Joined: Dec 2022
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.
Posts: 6,798
Threads: 20
Joined: Feb 2020
Dec-12-2022, 02:51 PM
(This post was last modified: Dec-12-2022, 02:53 PM by deanhystad.)
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)
Posts: 4,790
Threads: 76
Joined: Jan 2018
Dec-12-2022, 07:23 PM
(This post was last modified: Dec-12-2022, 07:26 PM by Gribouillis.)
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
Posts: 7
Threads: 2
Joined: Dec 2022
(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.
Posts: 7
Threads: 2
Joined: Dec 2022
(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!
Posts: 6,798
Threads: 20
Joined: Feb 2020
(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.
Posts: 7
Threads: 2
Joined: Dec 2022
(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!
Posts: 6,798
Threads: 20
Joined: Feb 2020
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.
Posts: 7
Threads: 2
Joined: Dec 2022
Dec-17-2022, 02:41 PM
(This post was last modified: Dec-17-2022, 02:42 PM by cametan_001.)
(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!
|