help with generators
George Sakkis
gsakkis at rutgers.edu
Thu May 19 05:27:26 EDT 2005
"Steven Bethard" wrote:
> It would help if you explained what you expected. But here's code
that
> prints about the same as your non-generator function.
>
> py> def bin(n):
> ... s = []
> ... def bin(n):
> ... if n == 0:
> ... yield s
> ... else:
> ... s.append(0)
> ... for s1 in bin(n - 1):
> ... yield s1
> ... s.pop()
> ... s.append(1)
> ... for s1 in bin(n - 1):
> ... yield s1
> ... s.pop()
> ... return bin(n)
> ...
> py> for s in bin(1):
> ... print s
> ...
> [0]
> [1]
> py> for s in bin(2):
> ... print s
> ...
> [0, 0]
> [0, 1]
> [1, 0]
> [1, 1]
>
> Note that to make the recursive calls work, you *must* iterate
through
> them, thus what was in your code:
>
> bin(n - 1)
>
> now looks like:
>
> for s1 in bin(n - 1):
> yield s1
>
> This is crucial. bin(n - 1) creates a generator object. But unless
you
> put it in a for-loop (or call it's .next()) method, the generator
will
> never execute any code.
>
> HTH,
>
> STeVe
A caveat of the implementation above: it yields the same instance (s),
which is unsafe if the loop variable is modified (e.g. in the "for s in
bin(n)" loop, s should not be modified). Moreover, each yielded value
should be 'consumed' and then discarded; attempting to store it (as in
list(bin(n))) references only the last yielded value.
Here's a shorter, clear and safe implementation:
def bin2(n):
if n:
for tail in bin2(n-1):
yield [0] + tail
yield [1] + tail
else:
yield []
It also turns out to be faster than the original despite yielding a new
list every time:
$ python /usr/local/lib/python2.4/timeit.py -s "from bin import bin"
"for i in bin(10): pass"
100 loops, best of 3: 5.21 msec per loop
$ python /usr/local/lib/python2.4/timeit.py -s "from bin import bin2"
"for i in bin2(10): pass"
100 loops, best of 3: 2.68 msec per loop
The reason is that the original computes the same permutations ("for s1
in bin(n - 1)") twice. Here's a faster version that preallocates the
list and sets the 0s and 1s for each permutation:
def bin3(n):
array = [None]*n
def _bin(n):
if not n:
yield array
else:
n -= 1
for perm in _bin(n):
# replace 'yield perm' with 'yield list(perm)' for safe
# mutation and accumulation of yielded lists
perm[n] = 0; yield perm
perm[n] = 1; yield perm
return _bin(n)
$ python /usr/local/lib/python2.4/timeit.py -s "from bin import bin3"
"for i in bin3(10): pass"
1000 loops, best of 3: 587 usec per loop
Cheers,
George
More information about the Python-list
mailing list