Parametrized generators? - python

I need parametrized generator. Such one that will accept parameters on the call to .next(arg).
In this specific case I want the generator to change with +1 when arg is True and -1 on False.
Is this possible in python ?

Using the .send method on a generator instance allows you to inject state into the generator. That makes something like this possible:
>>> def mygen():
... i = 0
... sign = 1
... while True:
... val = yield sign*i
... if val is not None:
... sign = 1 if val else -1
... i += 1
...
>>> g = mygen()
>>> next(g)
0
>>> next(g)
1
>>> next(g)
2
>>> g.send(False)
-3
>>> next(g)
-4
>>> next(g)
-5
>>> g.send(True)
6
>>> next(g)
7
Note that next(g) is equivalent to g.send(None).

Here is my final version :
def flip_flop(low=0, high=10):
i = 0
while i >= low and i <= high :
cond = yield i
if cond : i += 1
else : i -= 1
In [64]: ff = flip_flop()
In [65]: ff.next()
Out[65]: 0
In [66]: ff.send(True)
Out[66]: 1
In [67]: ff.send(True)
Out[67]: 2
In [68]: ff.send(True)
Out[68]: 3
In [69]: ff.send(False)
Out[69]: 2

Related

python - different behavior of print(*generator, <element>)

Question
Please help understand why the two cases act differently although both use a generator (i for i in range(5)).
>>> print(i for i in range(5))
<generator object <genexpr> at 0x7fc409c02900>
>>> print(*(i for i in range(5)))
0 1 2 3 4
>>> print(*(i for i in range(5)), 5)
0 1 2 3 4 5
>>> _r = (i for i in range(5))
>>> print(_r)
<generator object <genexpr> at 0x7fc409c029e0>
>>> print(*_r)
0 1 2 3 4
>>> print(*_r, 5)
5
>>> print(*(_r), 5)
5
When you use the * operator on a generator expression (or any iterator for that matter), it consumes it:
my_iterator = (i for i in range(3))
second_iterator = iter(list(range(3)))
# splat operator consumes the generator expression
print(*my_iterator)
0 1 2
print(*my_iterator, "Empty!")
Empty!
# splat operator also consumes the iterator
print(*second_iterator)
0 1 2
print(*second_iterator, "Also empty!")
Also empty!
You'd need to recreate it to re-use it, which is what you're doing in your first example:
s = (i for i in range(3))
print(*s)
0 1 2
# You create a new generator expression/iterator
s = (i for i in range(3))
print(*s)
0 1 2
# And so it isn't empty
# same with the shorthand
print(*(i for i in range(3))
0 1 2
Note on range
Since I've used range for this example, it's important to note that range doesn't share this behavior, but an iterator over range does:
x = range(3)
print(*x)
0 1 2
print(*x)
0 1 2
# the range object isn't consumed and can be re-used
a = iter(x)
print(*a)
0 1 2
print(*a)
# prints nothing because `a` has been exhausted
More detail can be found in this answer

How to get correct answer in for loop

I want to do multiplication using for loop. Here is the code.
a = 4
b = 6
for i in [a,b]:
i*=2
The values of a and b remain the same. How to make it work?
int are immutable, so you'll need to rebind a and b to fresh int objects
>>> a = 4
>>> b = 6
>>> a, b = (i*2 for i in [a,b])
>>> a
8
>>> b
12
Use dictionary:
z = {'i': a, 'j': b}
for k in z.keys():
z[k] *= 2
a = z['i']
b = z['j']

Effect of "and" on assignment and addition

A line of code has tripped me up:
>>> i = 1
>>> j = 1
>>> i += j > 0 and i
>>> print(i)
2
What is the underlying mechanic or system that makes this work? It seems like it's syntactic sugar for i = i + i if j > 0 else i, but that's a lot to unpack. Am I wrong? Is there another system in play I don't know?
Thanks!
EDIT:
For clarity:
>>> i = 3
>>> j = 2
>>> i += j > 1 and i
>>> i
6
Let's break it down:
In [1]: i = 1
In [2]: j = 1
Now, let's look at the expression i += j > 0 and i:
In [3]: j > 0
Out[3]: True
Because j, which is 1 is greater than 0, this evaluates to True.
In [4]: j > 0 and i
Out[4]: 1
Because j > 0 is True, the value of the boolean expression is the value of the right-hand side, namely 1.
Thus, i += j > 0 and i simplifies to i += i or i = i + i:
In [5]: i += i
In [6]: i
Out[6]: 2
Let's also consider your second example:
>>> i = 3
>>> j = 2
>>> i += j > 1 and i
>>> i
6
For the thrid line we have these transforms:
i += j > 1 and i
i = i + (j > 1 and i)
i = 3 + (2 > 1 and 3)
i = 3 + (True and 3)
i = 3 + 3
i = 6
In Python and and or do not return boolean values, but rather return one of the options presented to them which evaluates to the correct boolean value.
For example, and will return either the first False value it encounters, or the last True value:
>>> 1 and 3
3
>>> 1 and 0
0
Similarly, or will return the first True value it encounters, and otherwise return the first False value:
>>> 2 or 3
2
>>> 0 or 2
2
>>> False or 0
0
Basically, you should keep in mind that and and or do not necessarily return True/False, but return one of the elements presented to them which evaluates to True or False.

How to assign value inside list comprehension

How to assign a value to object's .ID attribute from [inside of a list comprehension]?
class C(object):
def __init__(self):
self.ID=0
l=[C() for i in range(4)]
print l
t=[c for c in l if c.ID+1]
for c in t: print c.ID,
[<main.C object at 0x10454de50>, <main.C object at
0x10454ded0>, <main.C object at 0x10454df90>, <main.C object
at 0x10455b150>]
0 0 0 0
[Finished in 0.1s]
edit
How to change i with list comprehension:
l=[i for i in range(4)]
i=0
t=[c for c in l if i++]
print i
You could define a simple function do perform the transformation:
def foo(c):
c.ID += 1
return c
Then use it in the list comprehension:
>>> t=[foo(c) for c in l]
>>> for c in t: print c.ID,
1 1 1 1
>>> for c in l: print c.ID,
1 1 1 1
Note that it may be a good idea to add an ID parameter to C's constructor, so you can construct C objects with any ID you want:
class C(object):
def __init__(self, ID=0):
self.ID=ID
Then
>>> a = [c(42) for _ in xrange(4)]
>>> b = [c(i) for i in xrange(4)]
>>> for c in a: print c.ID,
42 42 42 42
>>> for c in b: print c.ID,
>>> 0 1 2 3

Is there an easy way to get next and prev values in a for-loop?

So...is there an easy way to get next and previous values while iterating with a for-loop in Python?
I know this can be easily done if you do something like:
a = [3,4,5,6,3,4,5]
for x in range(len(a)):
next = a[x+1]
But what about:
for x in a:
x.next??
Here is a common pattern that I use to iterate over pairs of items in a sequence:
>>> a = range(10)
>>> for i, j in zip(a, a[1:]):
... print i, j
...
0 1
1 2
2 3
3 4
4 5
5 6
6 7
7 8
8 9
If you want three items (prev, item, next) you can do this:
>>> for i, j, k in zip(a, a[1:], a[2:]):
... print i, j, k
...
0 1 2
1 2 3
2 3 4
3 4 5
4 5 6
5 6 7
6 7 8
7 8 9
i is the previous element, j is the current element, k is the next element.
Of course, this "starts" at 1 and "ends" at 8. What should you receive as prev/next at the ends? Perhaps None? Probably easiest to just do this:
>>> a = [None] + a + [None]
>>> for i, j, k in zip(a, a[1:], a[2:]):
... print i, j, k
...
None 0 1
0 1 2
1 2 3
2 3 4
3 4 5
4 5 6
5 6 7
6 7 8
7 8 9
8 9 None
easiest way I know of is
for x,next in zip (a, a[1:]):
# now you have x and next available
You could always convert a into an iterator with iter and then iterate over that. This will allow you to use next inside the loop to advance the iterator that you are iterting over:
>>> a = [3,4,5,6,3,4,5]
>>> it = iter(a)
>>> for i in it:
... j = next(it, None)
... print('i={}, j={}'.format(i, j))
...
i=3, j=4
i=5, j=6
i=3, j=4
i=5, j=None
>>>
Also, the None in there is the default value to return if there is no next item. You can set it to whatever value you want though. Omitting the argument will cause a StopIteration exception to be raised:
>>> a = [1, 2, 3, 4, 5]
>>> it = iter(a)
>>> for i in it:
... j = next(it)
... print('i={}, j={}'.format(i, j))
...
i=1, j=2
i=3, j=4
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
StopIteration
>>>
If you want both the previous and the next element in a circular sequence for each iteration:
a = [3,4,5,6,3,4,5]
l = len(a)
for k, v in enumerate(a):
print a[(k-1)%l], v, a[(k+1)%l] #prints previous, current, next elements
this is easy too:
>>> a = [3,4,5,6,3,4,5]
>>> for i in range(1,len(a)):
... print a[i-1],a[i]
...
3 4
4 5
5 6
6 3
3 4
4 5
Probably overkill but I sometimes use the following more general generator for this, which yields a sequence of 'windows' of any size on a list or other iterable. (The window size must be less than the length of the iterable.)
def sliding_window(iterable, size):
try: # indexed iterable
for i in range(len(iterable) - size + 1):
yield iterable[i:i+size]
except TypeError: # iterator
window = [next(iterable) for _ in range(size)]
yield window
for item in iterable:
window = window[1:] + [item]
yield window
a = [3,4,5,6,3,4,5]
for current, following in sliding_window(a, 2):
print(current, following)

Categories

Resources