0

EDIT: Unlike python: flat zip, I'm asking about a list of lists, not two lists.

I have a list of lists, for example:

[[a,b,c], [d,e,f], [g,h,i,j,k]]

And I wish to interleave them such that we take the first of each list, then the second, then the third, etc. In this example case we would get:

[a,d,g,b,e,h,c,f,i,j,k]

My pretty naive implementation:

   output_list = []   

   while len(input_lists) > 0:
       for i in range(len(input_lists)):
           element = test_sets[i][0]
           input_lists[i] = input_lists[i][1:]
           output_list.append(element)

       input_lists = [l for l in input_lists if len(l) > 0]

What's a more elegant solution?

rodriper
  • 626
  • 7
  • 9
Omroth
  • 883
  • 7
  • 24
  • 1
    "like flattening a zip" — Yes, have you tried that…? – deceze Apr 25 '23 at 08:58
  • 1
    Yes I have @deceze - two problems. Zip cuts off lists whose lengths are longer than the min; and zip's argument is each list separately. – Omroth Apr 25 '23 at 09:03
  • *"zip's argument is each list separately."* The `*` operator converts an iterable to multiple function args (as in @rodriper 's solution) – slothrop Apr 25 '23 at 09:11

3 Answers3

3

If you want a one-liner use zip_longest() and chain() from itertools:

from itertools import chain, zip_longest
[i for i in chain(*zip_longest(*[[1,2,3], [4,5,6], [7,8,9,10,11]])) if i is not None]

[1, 4, 7, 2, 5, 8, 3, 6, 9, 10, 11]
gimix
  • 3,431
  • 2
  • 5
  • 21
  • I'd suggest to change 'if i' to 'if i is not None' if the lists contain numbers (not strings), otherwise you'll lose all the zeros. – rodriper Apr 25 '23 at 09:30
2

Here's a solution using zip_longest from itertools:

from itertools import zip_longest

def flatten_list(lst):
    # zip_longest('ABCD', 'xy', fillvalue='None') --> [[A,x], [B,y], [C,None], [D,None]
    transposed = zip_longest(*lst, fillvalue=None)
    flattened = [elem for sublist in transposed for elem in sublist if elem is not None]
    return flattened

EDIT: My upvote to @gimix for the one-liner

from itertools import chain, zip_longest

def flatten_list_v2(lst):
  # zip_longest('ABCD', 'xy', fillvalue='None') --> [[A,x], [B,y], [C,None], [D,None]
  return [i for i in chain(*zip_longest(*lst)) if i is not None]
rodriper
  • 626
  • 7
  • 9
0

Thanks to @Michael Butsher, the python itertools docs have this:

def roundrobin(*iterables):
    "roundrobin('ABC', 'D', 'EF') --> A D E B F C"
    # Recipe credited to George Sakkis
    num_active = len(iterables)
    nexts = cycle(iter(it).__next__ for it in iterables)
    while num_active:
        try:
            for next in nexts:
                yield next()
        except StopIteration:
            # Remove the iterator we just exhausted from the cycle.
            num_active -= 1
            nexts = cycle(islice(nexts, num_active))

Hardly a one liner, but faster than mine I'm sure.

Omroth
  • 883
  • 7
  • 24