Variable references the wrong iterator, even after confirmed reassignment - python

I have the following loop that reassigns to an iterator variable:
currentPrime = None
sieve = iter(range(2, 10))
while True:
try:
# The first value should be prime.
currentPrime = next(sieve)
except StopIteration:
# Stop when sieve is empty.
print(currentPrime)
break
print(currentPrime)
# Filter out all multiples of currentPrime.
sieve = (x for x in sieve if x % currentPrime)
#print(tuple(sieve))
Even though I apply a filter on each iteration of the loop, the output goes through the entire range:
2
3
4
5
6
7
8
9
9
If I uncomment the last print call, I see (3, 5, 7, 9), which means the filter and assignment to sieve work correctly, but the next(sieve) call somehow accesses the original iterator which no variable points to.
Any idea what's happening here? I'm using Python 3.7.0.

As user2357112 said, "currentPrime is looked up on use, not on generator creation."
One solution is to use filter with a lambda that localizes the current value of currentPrime. Note how the lambda uses a default argument to create a local variable:
currentPrime = None
sieve = iter(range(2, 10))
while True:
try:
# The first value should be prime.
currentPrime = next(sieve)
except StopIteration:
# Stop when sieve is empty.
print(currentPrime)
break
# Filter out all multiples of currentPrime.
sieve = filter(lambda x, prime=currentPrime: x % prime, sieve)

There is a stack of generators if I'm not wrong.
What happens (TL;DR: all generators refer to one currentPrime instance and refer lazy):
Get value 2 from range iterator.
Create generator expression (A). Variable currentPrime = 2 now and is NOT free (not in closure). Generator consume rest of range (3..9).
Get value 3 from generator A (3 % 2 is True)
Create generator expression (B). Variable currentPrime = 3 for both generators (A, B). Generator consume rest of generator A (4..9).
Get value 4 from generator B. See: next() → B.next(A) → A yields 4 (A checks: 4 % 3 is True), then B checks 4 % 3 is True.
Create generator expression (C) ... and so on.
Variable in in generator is not in closure, see:
>>> a = 5
>>> gen = (a for i in range(3))
>>> a = 3
>>> list(gen)
[3, 3, 3]

Related

Creating a new method

I'm working on a MOOC on Python Programming and am having a hard time finding a solution to a problem set. I hope you can provide some assistance.
The problem is:
The Fibonacci sequence is a number sequence where each number is the sum of the previous two numbers. The first two numbers are defined as 0 and 1, so the third number is
1 (0 + 1 = 1), the fourth number is 2 (1 + 1 = 2), the fifth number is 3 (1 + 2 = 3), the sixth number is 5(2 + 3 = 5), and so on.
Below we've started a class called FibSeq. At any time, FibSeq holds two values from the Fibonacci sequence: back1 and back2.
Create a new method inside FibSeq called next_number. The next_number method should:
Calculate and return the next number in the sequence,
based on the previous 2.
Update back2 with the former value of back1, and update
back1 with the new next item in the sequence.
This means that consecutive calls to next_number should yield each consecutive number from the Fibonacci sequence. Calling next_number 5 times would print 1, 2, 3, 5, and 8.
My code is below:
class FibSeq:
def __init__(self):
self.back1 = 1
self.back2 = 0
def next_number(self):
self.back1 = self.back1 + self.back2
self.back2 = self.back1 - self.back2
yield(self.back1)
f = FibSeq()
for i in range(5):
s = f.next_number()
print(next(s))
My code returns the following:
1
2
3
5
8
<generator object FibSeq.next_number at 0x7f68f6fbe678>
<generator object FibSeq.next_number at 0x7f68f6fbe678>
<generator object FibSeq.next_number at 0x7f68f6fbe678>
<generator object FibSeq.next_number at 0x7f68f6fbe678>
<generator object FibSeq.next_number at 0x7f68f6fbe678>
However, it should only return 1,2,3,5,8, after running the below code:
newFib = FibSeq()
print(newFib.next_number())
print(newFib.next_number())
print(newFib.next_number())
print(newFib.next_number())
print(newFib.next_number())
Why does my code return the last 5 "error" statements like this <generator object FibSeq.next_number at 0x7f68f6fbe678>?
Thank you.
Although I agree that a generator is a much more pythonic solution to this problem, I don't believe the assignment wants you to write a generator. What it says is:
This means that consecutive calls to next_number should yield each consecutive number from the Fibonacci sequence.
Their use of the word "yield" (rather than the more natural "return") is confusing, to be sure, but "return" is surely what they meant because each call to a generator function returns an iterator, not a value, and you need to call next on the returned iterator to get successive values.
Generators and class instances are two different ways of solving the same problem, which is how to retain state while producing successive values of a sequence. In the object-oriented solution, you create an object by calling the class' constructor, and then you repeatedly call an instance method (such as next_number. That would lead to a solution like this:
class FibSeq:
def __init__(self):
self.back1, self.back2 = 1, 0
def next_number(self):
self.back1, self.back2 = self.back1 + self.back2, self.back1
return self.back1
# Example usage:
fibber1 = FibSeq()
for i in range(1, 6):
print(i, fibber1.next_number())
That prints
1 1
2 2
3 3
4 5
5 8
(Note that Fib(0) is also 1. To my mind, a Fibonacci generator should start at 0, but to conform to the expectation that the first five calls to next_number, I started the counter at 1.)
If we want another sequence starting at the beginning, we just create another instance of the class. We can then use both instances independently. Here I print out the next five values produced by the first object, whose next value produced will be Fib(6), and in another column, I output the first five values produced by a new object:
fibber2 = FibSeq()
for i in range(6, 11):
print(i, fibber2.next_number(), fibber1.next_number())
That prints
6 1 13
7 2 21
8 3 34
9 5 55
10 8 89
And it's easy to see that the two instances of FibSeq are each using their own state (that is, the members back1 and back2).
But that's not very Pythonic (in my opinion). In Python, we can create a generator function to do the same thing; the generator's state is now contained in local variables, not so easily available for inspection. Calling the generator function returns an iterator --a different one each time, with its own local variables-- and the iterators can be used in a for statement or a comprehension. Generators are distinguished from ordinary functions by the fact that they include at least one yield statement. They normally don't include a return statement, or if they do, it doesn't return anything. This has nothing to do with the return value of calling the generator; as I said, calling the generator returns an iterator, but that's done automatically as soon as you call the generator, before any statement has been executed.
So here's the generator version of the Fibonacci sequence:
def FibGen():
back1, back2 = 1, 0
while True:
back1, back2 = back1 + back2, back1
yield back1
Now, we can use a simple for loop, but with a bit of caution because the iterator produced by FibGen never stops. To specify the desired number of values generated, I can zip it with a range iterator:
for i, f in zip(range(1, 6), FibGen()):
print(i, f)
# Output:
1 1
2 2
3 3
4 5
5 8
This works because zip stops iterating as soon as one of its argument iterators is done.
In that for loop, I created the iterator without saving it. But I might want to save it, in order to do something like the side-by-side display from the O-O example. That works in a very similar fashion:
# Create one iterator and print the first five values
fibber1 = FibGen()
for i, f in zip(range(1, 6), fibber1):
print(i, f)
# Now create another, independent iterator:
fibber2 = FibGen()
for i, f1, f2 in zip(range(6, 11), fibber1, fibber2):
print(i, f1, f2)
The output from the two loops:
1 1
2 2
3 3
4 5
5 8
6 1 13
7 2 21
8 3 34
9 5 55
10 8 89
I can also use the generator to fill in a list comprehension, although again I need to be careful to stop the generation, since I have enough memory to store an infinite list. Again, zipping with a range is a simple way to control the generation count:
v = [f for i,f in zip(range(10), FibGen())]
print(v)
# Output:
[1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
Collecting generated values is obviously simpler for iterators which only generate a finite number of values. But on the whole, you should avoid collecting generated values. It's surprising how rarely you actually need to save the list.

How to create a generator function that produces a specific number of values?

I am trying to create a generator function that accepts 1 or 1+ arguments. It should return an iterable of the left overvalues of the iterable with the most values after the other iterabless no longer produce values.
For example:
for i in func('dfg', 'dfghjk', [1,2]):
print(i)
prints -> h
j
k
because the argument 'dfg', which has the second most values, stop producing values at 3.
The argument 'dfghjk', which has the most values, then yields 'hjk' (the leftover values).
The catch here is that the iterables may or may not be finite, so I am not able to compute the length of the argument or add it to other data structures.
Any help would be great, thanks!
To solve your problem I'd try to manually exhaust all the iterators until only one remains.
def foo(*iterables):
iterators = [iter(x) for x in iterables]
while len(iterators) > 1:
for idx, it in enumerate(iterators[:]):
try:
peek = next(it)
except StopIteration:
# remove the iterator when exhausted
iterators.pop(idx)
yield peek
yield from iterators[0]
Notice that for it to work you will need to ensure that the iterables are indeed iterators (converting them with iter). You also need to store the last element obtained from the iterator. Notice that because we only use the iterator interface infinite iterators work just fine:
>>> it = func([1], itertools.count(), [2,2])
>>> for _ in range(3): print(next(it))
2
3
4

Multiply squares function

I've been trying to write a function that can take a different number of integer parameters and return the product of the squares of the parameters.
output should be:
multiply_squares(3,2,1)
multiply_squares(2,5)
36
100
So far I've been playing around with this:
def multiply_squares(*num_tuple):
result = []
for num in num_tuple:
c = num_tuple[num]**2
result.append(c)
for i in result:
d *= result[i]
print(d)
multiply_squares(3,2,1)
multiply_squares(2,5)
I've been getting "tuple index out of range" and haven't been able to solve this properly. Could anyone help me out? And also show me how to turn this into a recursive function?(if possible)
I got a test on Tuesday and I'm trying to understand the logic behind this :)
Any feedback/guidance is appreciated !
for num in num_tuple means iterate over num_tuple and set each value to num. num isn't the index of the tuple.
def multiply_squares(*num_tuple):
result = []
for num in num_tuple:
c = num**2
result.append(c)
d = 1
for i in result:
d *= i
print(d)
return d
multiply_squares(3,2,1)
multiply_squares(2,5)
Regarding recursive solution:
def multiply_squares(*num_tuple):
if not num_tuple: # Base case:
return 1
return (num_tuple[0] ** 2) * multiply_squares(*num_tuple[1:])
Why the need for the intermediary list?
def multiply_squares(*num_tuple):
r = 1
for n in num_tuple:
r *= n * n
return r
and it works:
>>> multiply_squares(3,2,1)
36
>>> multiply_squares(2,5)
100
Recursively:
def multiply_squares(*num_tuple):
return num_tuple[0] ** 2 * multiply_squares(*num_tuple[1:]) if num_tuple else 1
Just to help with the understanding, I'll just print the inputs to each function call:
>>> multiply_squares(3,2,1)
(3, 2, 1)
(2, 1)
(1,)
()
36
So each guy who gets called knows: its job and its inputs (the numbers). So all it needs to return (either to the initial caller (you) or the guy above him) is what the product of the squares of inputs are.
To do this, the guy can break down his problem by just multiplying the square of the first input to whatever the result of the product of the squares of the other inputs is (through recursively calling himself).
And this will work perfectly... Until, eventually, there are no other inputs. In this case, what does the guy do? Well, this is a special case which will only be instantiated by another person solving the same problem as he is. And the person above him is relying on him to return the product of the squares of that guy's remaining inputs so he can multiply his result to that result. For that reason, he must return 1 otherwise the whole system wouldn't work.
In Python we can test whether the function had no inputs by checking the truthiness of the input tuple. This just so happens to equate to checking whether that data structure has any elements.
I.e.
bool(tuple()) #empty tuple
#False
bool((1,2,3)) #populated tuple
#True
We could just as easily checked whether the length of the tuple was equal (or in this case not equal) to 0.
I.e.
len(tuple()) != 0
#False
len((1,2,3)) != 0
#True
So walking it through:
num_tuple[0] ** 2 * multiply_squares(*num_tuple[1:]) if num_tuple else 1
# ^ ^ ^ ^ ^
# 1st input sqrd | by the prod of sqrs of rest | given inputs | otherwise 1
# multiplied only if
Why doesn't your code work:
The line:
for num in num_tuple:
iterates over the elements of the num_tuple tuple, not the indicies.
This means that in the first example, num will be 3, then 2 and then 1. Then, instead of indexing the num_tuple with what you thought were the indicies (0, 1 and 2) you are instead indexing with these inputs to the function.
This results in the out of range error as you try to index the tuple with 3 when the length of the tuple is only three elements long (making [2] the last index).
To remedy your solution, you could either change that line to:
for index in range(num_tuple):
and then your indexing will work (if you also replace all previous occurrences of num with index).
Alternatively, a better solution would be to scrap the indexing altogether and just use the elements directly as that is what you want.
That would look something like:
def multiply_squares(*num_tuple):
result = []
for num in num_tuple:
c = num**2
result.append(c)
r = 1
for i in result:
r *= i
return r
which now works as expected.
Please have a look at this two-line solution (because of the assertion) using functools.reduce:
from functools import reduce
a = (3, 2, 1)
b = (2, 5)
c = 2
def multiply_squares(*nums):
assert nums # make sure we get input
return reduce(lambda x, y: x * (y**2), nums, 1)
print(multiply_squares(*a))
print(multiply_squares(*b))
print(multiply_squares(c))
print(multiply_squares())
'''Output:
36
100
4
Traceback (most recent call last):
File "test.py", line 16, in <module>
print(multiply_squares())
File "test.py", line 9, in multiply_squares
assert nums
AssertionError
'''
which is actually very robust.

In this short recursive function `list_sum(aList)`, the finish condition is `if not aList: return 0`. I see no logic in why this condition works

I am learning the recursive functions. I completed an exercise, but in a different way than proposed.
"Write a recursive function which takes a list argument and returns the sum of its integers."
L = [0, 1, 2, 3, 4] # The sum of elements will be 10
My solution is:
def list_sum(aList):
count = len(aList)
if count == 0:
return 0
count -= 1
return aList[0] + list_sum(aList[1:])
The proposed solution is:
def proposed_sum(aList):
if not aList:
return 0
return aList[0] + proposed_sum(aList[1:])
My solution is very clear in how it works.
The proposed solution is shorter, but it is not clear for me why does the function work. How does if not aList even happen? I mean, how would the rest of the code fulfill a not aList, if not aList means it checks for True/False, but how is it True/False here?
I understand that return 0 causes the recursion to stop.
As a side note, executing without if not aList throws IndexError: list index out of range.
Also, timeit-1million says my function is slower. It takes 3.32 seconds while the proposed takes 2.26. Which means I gotta understand the proposed solution.
On the call of the function, aList will have no elements. Or in other words, the only element it has is null. A list is like a string or array. When you create a variable you reserve some space in the memory for it. Lists and such have a null on the very last position which marks the end so nothing can be stored after that point. You keep cutting the first element in the list, so the only thing left is the null. When you reach it you know you're done.
If you don't use that condition the function will try to take a number that doesn't exist, so it throws that error.
You are counting the items in the list, and the proposed one check if it's empty with if not aList this is equals to len(aList) == 0, so both of you use the same logic.
But, you're doing count -= 1, this has no sense since when you use recursion, you pass the list quiting one element, so here you lose some time.
According to PEP 8, this is the proper way:
• For sequences, (strings, lists, tuples), use the fact that empty
sequences are false.
Yes: if not seq:
if seq:
No: if len(seq)
if not len(seq)
Here is my amateur thougts about why:
This implicit check will be faster than calling len, since len is a function to get the length of a collection, it works by calling an object's __len__ method. This will find up there is no item to check __len__.
So both will find up there is no item there, but one does it directly.
not aList
return True if there is no elements in aList. That if statement in the solution covers edge case and checks if input parameter is not empty list.
For understand this function, let's run it step by step :
step 0 :
L=[0,1,2,3,4]
proposed_sum([0,1,2,3,4])
L != []
return l[0] + proposed_sum([1,2,3,4])
step 1 calcul proposed_sum([1,2,3,4]):
proposed_sum([1,2,3,4])
L != []
return l[0] + sum([2,3,4])
step 2 calcul proposed_sum([2,3,4]):
proposed_sum([2,3,4])
L != []
return l[0] + sum([3,4])
step 3 calcul proposed_sum([3,4]):
proposed_sum([3,4])
L != []
return l[0] + sum([4])
step 4 calcul proposed_sum([4]):
proposed_sum([4])
L != []
return l[0] + sum([])
step 5 calcul proposed_sum([]):
proposed_sum([])
L == []
return 0
step 6 replace:
proposed_sum([0,1,2,3,4])
By
proposed_sum([]) + proposed_sum([4]) + proposed_sum([3,4]) + proposed_sum([2,3,4]) + proposed_sum([1,2,3,4])+ proposed_sum([0,1,2,3,4])
=
(0) + 4 + 3 + 2 + 1 + 0
Python considers as False multiple values:
False (of course)
0
None
empty collections (dictionaries, lists, tuples)
empty strings ('', "", '''''', """""", r'', u"", etc...)
any other object whose __nonzero__ method returns False
in your case, the list is evaluated as a boolean. If it is empty, it is considered as False, else it is considered as True. This is just a shorter way to write if len(aList) == 0:
in addition, concerning your new question in the comments, consider the last line of your function:
return aList[0] + proposed_sum(aList[1:])
This line call a new "instance" of the function but with a subset of the original list (the original list minus the first element). At each recursion, the list passed in argument looses an element and after a certain amount of recursions, the passed list is empty.

How to change for-loop iterator variable in the loop in Python?

I want to know if is it possible to change the value of the iterator in its for-loop?
For example I want to write a program to calculate prime factor of a number in the below way :
def primeFactors(number):
for i in range(2,number+1):
if (number%i==0)
print(i,end=',')
number=number/i
i=i-1 #to check that factor again!
My question : Is it possible to change the last two line in a way that when I change i and number in the if block, their value change in the for loop!
Update: Defining the iterator as a global variable, could help me? Why?
Short answer (like Daniel Roseman's): No
Long answer: No, but this does what you want:
def redo_range(start, end):
while start < end:
start += 1
redo = (yield start)
if redo:
start -= 2
redone_5 = False
r = redo_range(2, 10)
for i in r:
print(i)
if i == 5 and not redone_5:
r.send(True)
redone_5 = True
Output:
3
4
5
5
6
7
8
9
10
As you can see, 5 gets repeated. It used a generator function which allows the last value of the index variable to be repeated. There are simpler methods (while loops, list of values to check, etc.) but this one matches you code the closest.
No.
Python's for loop is like other languages' foreach loops. Your i variable is not a counter, it is the value of each element in a list, in this case the list of numbers between 2 and number+1. Even if you changed the value, that would not change what was the next element in that list.
The standard way of dealing with this is to completely exhaust the divisions by i in the body of the for loop itself:
def primeFactors(number):
for i in range(2,number+1):
while number % i == 0:
print(i, end=',')
number /= i
It's slightly more efficient to do the division and remainder in one step:
def primeFactors(number):
for i in range(2, number+1):
while True:
q, r = divmod(number, i)
if r != 0:
break
print(i, end=',')
number = q
The only way to change the next value yielded is to somehow tell the iterable what the next value to yield should be. With a lot of standard iterables, this isn't possible. however, you can do it with a specially coded generator:
def crazy_iter(iterable):
iterable = iter(iterable)
for item in iterable:
sent = yield item
if sent is not None:
yield None # Return value of `iterable.send(...)`
yield sent
num = 10
iterable = crazy_iter(range(2, 11))
for i in iterable:
if not num%i:
print i
num /= i
if i > 2:
iterable.send(i-1)
I would definitely not argue that this is easier to read than the equivalent while loop, but it does demonstrate sending stuff to a generator which may gain your team points at your next local programming trivia night.
It is not possible the way you are doing it. The for loop variable can be changed inside each loop iteration, like this:
for a in range (1, 6):
print a
a = a + 1
print a
print
The resulting output is:
1
2
2
3
3
4
4
5
5
6
It does get modified inside each for loop iteration.
The reason for the behavior displayed by Python's for loop is that, at the beginning of each iteration, the for loop variable is assinged the next unused value from the specified iterator. Therefore, whatever changes you make to the for loop variable get effectively destroyed at the beginning of each iteration.
To achieve what I think you may be needing, you should probably use a while loop, providing your own counter variable, your own increment code and any special case modifications for it you may need inside your loop. Example:
a = 1
while a <= 5:
print a
if a == 3:
a = a + 1
a = a + 1
print a
print
The resulting output is:
1
2
2
3
3
5
5
6
Yes, we can only if we dont change the reference of the object that we are using. If we can edit the number by accessing the reference of number variable, then what you asked is possible.
A simple example:
a=[1,2,3]
a=a+[4]==>here, a new object is created which plots to different address.
a+=[4]==>here , the same object is getting updated which give us the desired result.
number=10
list1=list(range(2,number+1))
# list1
for i in list1:
print(list1,i)
if (number%i==0):
print(i,end=',')
number=number//i #we can simply replace it with number//=i to edit the number without changing the reference or without creating a new object.
try:
[list1.pop() for i in range(10,0,-1) if(i>number)]
#here pop() method is working on the same object which list created by number refers. so, we can able to change the iterable in the forloop.
except:
continue
i=i-1 #to check that factor again!

Categories