Reputation: 77127
I have a list of lists such that the length of each inner list is either 1 or n (assume n > 1).
>>> uneven = [[1], [47, 17, 2, 3], [3], [12, 5, 75, 33]]
I want to transpose the list, but instead of truncating the longer list (as with zip
) or filling the shorter lists with None
, I want to fill the shorter lists with their own singular value. In other words, I'd like to get:
>>> [(1, 47, 3, 12), (1, 17, 3, 5), (1, 2, 3, 75), (1, 3, 3, 33)]
I can do this with a couple of iterations:
>>> maxlist = len(max(*uneven, key=len))
>>> maxlist
4
>>> from itertools import repeat
>>> uneven2 = [x if len(x) == maxlist else repeat(x[0], maxlist) for x in uneven]
>>> uneven2
[[1, 1, 1, 1], [47, 17, 2, 3], [3, 3, 3, 3], [12, 5, 75, 33]]
>>> zip(*uneven2)
[(1, 47, 3, 12), (1, 17, 3, 5), (1, 2, 3, 75), (1, 3, 3, 33)]
But is there a better approach? Do I really need to know maxlist
in advance to accomplish this?
Upvotes: 10
Views: 1246
Reputation: 77127
I really liked @chris-morgan's idea of simulating itertools.izip_longest
, so when I finally had the inspiration, I wrote an izip_cycle
function.
def izip_cycle(*iterables, **kwargs):
"""Make an iterator that aggregates elements from each of the iterables.
If the iterables are of uneven length, missing values are filled-in by cycling the shorter iterables.
If an iterable is empty, missing values are fillvalue or None if not specified.
Iteration continues until the longest iterable is exhausted.
"""
fillvalue = kwargs.get('fillvalue')
counter = [len(iterables)]
def cyclemost(iterable):
"""Cycle the given iterable like itertools.cycle, unless the counter has run out."""
itb = iter(iterable)
saved = []
try:
while True:
element = itb.next()
yield element
saved.append(element)
except StopIteration:
counter[0] -= 1
if counter[0] > 0:
saved = saved or [fillvalue]
while saved:
for element in saved:
yield element
iterators = [cyclemost(iterable) for iterable in iterables]
while iterators:
yield tuple([next(iterator) for iterator in iterators])
print list(izip_cycle([], range(3), range(6), fillvalue='@'))
Upvotes: 0
Reputation: 19329
You could use itertools.cycle()
instead:
>>> from itertools import cycle
>>> uneven3 = [x if len(x) != 1 else cycle(x) for x in uneven]
>>> zip(*uneven3)
[(1, 47, 3, 12), (1, 17, 3, 5), (1, 2, 3, 75), (1, 3, 3, 33)]
That means you don't need to know maxlist
ahead of time.
Upvotes: 4
Reputation: 97291
You can repeat one element list forever:
uneven = [[1], [47, 17, 2, 3], [3], [12, 5, 75, 33]]
from itertools import repeat
print zip(*(repeat(*x) if len(x)==1 else x for x in uneven))
Upvotes: 7