0

Given multiple lists like the ones shown:

a = [1, 2, 3]
b = [5, 6, 7, 8]
c = [9, 0, 1]
d = [2, 3, 4, 5, 6, 7]
...

I want to be able to combine them to take as many elements from the first list as I can before starting to take elements from the second list, so the result would be:

result = [1, 2, 3, 8, 6, 7]

Is there a particularly nice way to write this? I can't think of a really simple one without a for loop. Maybe a list comprehension with a clever zip.

mkrieger1
  • 19,194
  • 5
  • 54
  • 65
user2662833
  • 1,005
  • 1
  • 10
  • 20

3 Answers3

5

Simple slicing and concatenation:

a + b[len(a):]

Or with more lists:

res = []
for lst in (a, b, c, d):
    res += lst[len(res):]
# [1, 2, 3, 8, 6, 7]
user2390182
  • 72,016
  • 6
  • 67
  • 89
  • 1
    Oh okay. Yes, I didn't include this in my question. But I wanted a solution that works with as many lists as you give it. But this is definitely correct for what I asked (doh!) I better make an edit. – user2662833 Oct 05 '18 at 10:48
  • Yep, this is genius! Thank you for that :D I had to stop for a while to realise what it was doing – user2662833 Oct 05 '18 at 11:59
2

With itertools.zip_longest() for Python 3, works on any number of input lists:

>>> from itertools import zip_longest
>>> [next(x for x in t if x is not None) for t in zip_longest(a,b,c,d)]
[1, 2, 3, 8, 6, 7]

The default fill value is None so take the first none None element in each tuple created with the zip_longest call (you can change the defaults and criteria if None is a valid data value)

Chris_Rands
  • 38,994
  • 14
  • 83
  • 119
  • I like the approach, but the comprehensive generation of all tuples `t` makes this rather unperformant as all elements of all lists are touched once. – user2390182 Oct 05 '18 at 10:58
  • @schwobaseggl it might be slower than your solution but hard to be sure without timing on different data sets, your solution (which I also like) also has the `len()` calls and the slower `for` loop etc. – Chris_Rands Oct 05 '18 at 11:01
  • Sure, needs to be tested and probably depends on the actual data. Algorithmically speaking, however, mine should be `O(L)` (L being the length of the longest list) as slicing and extending is `O(k)` (k being length of the slice/extension) and `len` being `O(1)`. Yours should be `O(N*L)`. Still, the underlying thought *"Take first existing elemnt at given index"* is nicely reflected. – user2390182 Oct 05 '18 at 11:07
  • @schwobaseggl Agreed! – Chris_Rands Oct 05 '18 at 11:16
  • A minor variation: `[next(filter(None.__ne__, t)) for t in zip_longest(*src)]` ;) – PM 2Ring Oct 05 '18 at 11:18
0

With functools.reduce:

from functools import reduce
print(list(reduce(lambda a, b: a + b[len(a):], [a, b, c, d])))

This outputs:

[1, 2, 3, 8, 6, 7]
blhsing
  • 91,368
  • 6
  • 71
  • 106
  • schwobaseggl's version is more efficient since the loop extends the result list. Whereas using `reduce` like that creates a new list object for each temporary result. This is similar to doing `sum(x, [])`; see [here](https://stackoverflow.com/a/49887692/4014959) for why that's bad. – PM 2Ring Oct 05 '18 at 11:31