views:

256

answers:

3

I like to use the following idiom for combining lists together, sometimes:

>>> list(itertools.chain(*[[(e, n) for e in l] for n, l in (('a', [1,2]),('b',[3,4]))]))
[(1, 'a'), (2, 'a'), (3, 'b'), (4, 'b')]

(I know there are easier ways to get this particular result, but it comes comes in handy when you want to iterate over the elements in lists of lists of lists, or something like that. The trouble is, when you use generator expressions, this becomes error prone. E.g.

>>> list(itertools.chain(*(((e, n) for e in l) for n, l in (('a', [1,2]),('b',[3,4])))))
[(1, 'b'), (2, 'b'), (3, 'b'), (4, 'b')]

What's happening here is that the inner generator expressions get passed as arguments to itertools.chain, so at the the time they're evaluated, the outer generator expression has finished, and n is fixed at its final value, 'b'. I'm wondering whether anyone has thought of ways to avoid this kind of error, beyond "don't do that."

+5  A: 

wouldn't a nested list comprehension be more appropriate?

>>> tt = (('a', [1,2]),('b',[3,4]))
>>> [(s, i) for i, l in tt for s in l]
[(1, 'a'), (2, 'a'), (3, 'b'), (4, 'b')]
SilentGhost
You can stick it all in the one list comprehension, in this case, but it's more complicated to do that in some cases. I simplified the example for clarity.
fivebells
why do stick it into a single line? why not just nested for loops? that would be both more clear and won't require any intermediate lists to be built.
SilentGhost
but isn't question about 'generator expressions' not working ?
Anurag Uniyal
but to me it seems it's not a question at all. OP is quite clear why generators don't work. the only unclear thing here is why is he trying to use them?
SilentGhost
+1  A: 

Your approach almost works, you just need to flatten the generators. See how the for e in l is moved to the very right

>>> list(itertools.chain((e, n) for n, l in (('a', [1,2]),('b',[3,4])) for e in l ))
[(1, 'a'), (2, 'a'), (3, 'b'), (4, 'b')]

Here is how to do the same thing using itertools.product

>>> X=itertools.chain(*(itertools.product(*i[::-1]) for i in (('a', [1,2]),('b',[3,4]))))
>>> print list(X)
[(1, 'a'), (2, 'a'), (3, 'b'), (4, 'b')]

or if you are allowed to switch the tuples around

>>> X=itertools.chain(*(itertools.product(*i) for i in (([1,2],'a'),([3,4],'b'))))
>>> print list(X)
[(1, 'a'), (2, 'a'), (3, 'b'), (4, 'b')]
gnibbler
Thanks, the product function looks like it ought to be safer and lead to clearer expressions.
fivebells
A: 

I'm going to suggest

data = (('a', [1,2]), ('b', [3,4]))

result = []
for letter, numbers in data:
     for number in numbers:
         result.append((number, letter))

It's a lot more readable than your solution family. Fancy is not a good thing.

Mike Graham