Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

python 2 strange list comprehension behaviour

I was looking around list comprehension and saw smth strange. Code:

a = ['a', 'a', 'a', 'b', 'd', 'd', 'c', 'c', 'c']
print [(len(list(g)), k) if len(list(g)) > 1 else k for k, g in groupby(a)]

Result:

[(0, 'a'), 'b', (0, 'd'), (0, 'c')]

But I wanted to see:

[(3, 'a'), 'b', (2, 'd'), (3, 'c')]

What's the cause of such behaviour?

like image 292
Сергей Фролов Avatar asked Aug 04 '15 23:08

Сергей Фролов


2 Answers

When you call list() on an itertools._grouper object, you exhaust the object. Since you're doing this twice, the second instance results in a length of 0.

First:

if len(list(g))

now it's exhausted. Then:

(len(list(g)), k))

It will have a length of 0.

You can nest a generator/comprehension in your list comprehension to exhaust the object and save the relevant data before processing it:

>>> [(y,x) if y>1 else x for x,y in ((k, len(list(g))) for k, g in groupby(a))]
[(3, 'a'), 'b', (2, 'd'), (3, 'c')]
like image 178
TigerhawkT3 Avatar answered Oct 03 '22 12:10

TigerhawkT3


You need to ensure the elements of g are consumed only once:

>>> print [(len(list(g)), k) if len(list(g)) > 1 else k for k, g in ((k, list(g)) for k, g in groupby(a))]
[(3, 'a'), 'b', (2, 'd'), (3, 'c')]

This code will also iterate over k, g in groupby(a) but it'll turn g into a list object. The rest of the code can then access g as many times as needed (to check the length) without consuming the results.

Before making this change, g was a itertools._grouper object which means that you can iterate over g only once. After that it'll be empty and you won't be able to iterate over it again. That's why you're seeing length 0 appear in your results.

like image 21
Simeon Visser Avatar answered Oct 03 '22 14:10

Simeon Visser