1

Sorry, I don't know how to set a decent title for this problem.

I have a function that needs to take a list of lists, e.g. ll = [['a', 'b'], ['c'], ['d', 'e', 'f']] and return ['a', 'c', 'd', 'b', 'e', 'f'] (so, it doesn't just flatten it).

I have a feeling it is not as efficient as it could be.

def func(lol):
    ''' 
    Takes a list of lists of varying lengths and creates a single list by taking one by one element from each list respectively. E.g.:
    ll = [[1, 2, 3, 4], [5, 6], [7, 8, 9]]
    In: func(ll)
    Out: [1, 5, 7, 2, 6, 8, 3, 9, 4]
    '''
    arr = []
    while lol:
        for list in lol:
            try:
                arr.append(list.pop(0))
            except:
                lol= [list for list in lol if list]
    return arr

What would be a better/faster way to solve this?

martineau
  • 119,623
  • 25
  • 170
  • 301
P. Prunesquallor
  • 561
  • 1
  • 10
  • 26
  • `output = [i for elem in ll for i in elem]`? – Vasilis G. May 01 '18 at 19:15
  • @Prunesquallor I'm ready to believe you, but trying the answer in the linked question with your example works for me, so if that question is indeed not a duplicate, you should probably give an explanation as to why and what didn't work for you – William Perron May 01 '18 at 19:15
  • 1
    @WilliamPerron No problem, added 3 more – cs95 May 01 '18 at 19:18

1 Answers1

3

This is one way using itertools.

from itertools import zip_longest, chain

lst = [['a', 'b'], ['c'], ['d', 'e', 'f']]

res = list(filter(None, chain.from_iterable(zip_longest(*lst))))

['a', 'c', 'd', 'b', 'e', 'f']

The nice aspect of this solution is each step is lazy, i.e. zip_longest, chain.from_iterable and filter do not build intermediary lists.


The above logic can also be implemented via a list comprehension:

res = [i for x in zip_longest(*lst) for i in x if i]
jpp
  • 159,742
  • 34
  • 281
  • 339
  • @scnerd, Agreed, that's an alternative too, updated. – jpp May 01 '18 at 19:12
  • @cᴏʟᴅsᴘᴇᴇᴅ `list(chain.from_iterable(zip_longest(*lst)))` produces `['a', 'c', 'd', 'b', None, 'e', None, None, 'f']`. But I also don't like `filter(None, ...)`. What if input lists contain `None`'s among their values as a valid data? We need a sentinel object here and do `filter(lambda x: x is not sentinel, ...)` trick. – Ivan Velichko May 01 '18 at 19:34
  • @IvanVelichko I said the `fillvalue` wasn't necessary, but never said anything about `filter`, that's still needed if you want to remove those falsey values. Unfortunately, the downside to this is falsey values such as 0 and 0.0, and False will also be removed, if the input has them. In those cases, a lambda is imperative, can't get over it. – cs95 May 01 '18 at 19:36
  • @cᴏʟᴅsᴘᴇᴇᴅ sure. I meant that for a *fair* solution we need both - `fillvalue=sentinel` and a custom `filter(lambda x: x is not sentinel, ...)`. – Ivan Velichko May 01 '18 at 19:38