10

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?

2
  • 1
    You could simulate itertools.izip_longest, just using the last value of the sequence instead of a fillvalue when the sequence is exhausted. Commented May 16, 2012 at 0:24
  • @ChrisMorgan is right have saw this link docs.python.org/library/itertools.html maybe you could also use some other functions from there. Commented May 16, 2012 at 0:35

3 Answers 3

7

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))
Sign up to request clarification or add additional context in comments.

1 Comment

This was very helpful because the n=1 inner lists were manufactured. Now I've refactored so the list is [repeat(1), [47, 17, 2, 3], repeat(3), [12, 5, 75, 33]] and I can just do zip(*uneven).
4

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.

Comments

0

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='@'))

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.