96

I have two lists, the first of which is guaranteed to contain exactly one more item than the second. I would like to know the most Pythonic way to create a new list whose even-index values come from the first list and whose odd-index values come from the second list.

# example inputs
list1 = ['f', 'o', 'o']
list2 = ['hello', 'world']

# desired output
['f', 'hello', 'o', 'world', 'o']

This works, but isn't pretty:

list3 = []
while True:
    try:
        list3.append(list1.pop(0))
        list3.append(list2.pop(0))
    except IndexError:
        break

How else can this be achieved? What's the most Pythonic approach?

davidchambers
  • 20,922
  • 14
  • 68
  • 95
  • 2
    possible duplicate of [Alternating between iterators in Python](http://stackoverflow.com/questions/2017895/alternating-between-iterators-in-python) – Felix Kling Sep 09 '10 at 17:17
  • Not a duplicate! The accepted answer in the above-linked article produces a list of tuples, not a single, merged list. – Paul Sasik Sep 09 '10 at 17:21
  • @Paul: Yes, the accepted answer does not give the complete solution. Read the comments and the other answers. The question is basically the same and the other solutions can be applied here. – Felix Kling Sep 09 '10 at 17:23
  • 3
    @Felix: I respectfully disagree. It is true, the questions are in the same neighborhood but not really duplicates. As vague proof take a look at the potential answers here and compare with the other question. – Paul Sasik Sep 09 '10 at 17:28
  • Check out these: http://stackoverflow.com/questions/7529376/pythonic-way-to-mix-two-lists – wordsforthewise Aug 09 '16 at 22:22

21 Answers21

128

Here's one way to do it by slicing:

>>> list1 = ['f', 'o', 'o']
>>> list2 = ['hello', 'world']
>>> result = [None]*(len(list1)+len(list2))
>>> result[::2] = list1
>>> result[1::2] = list2
>>> result
['f', 'hello', 'o', 'world', 'o']
Duncan
  • 79,697
  • 10
  • 108
  • 148
  • 4
    Thanks, Duncan. I did not realize that it's possible to specify a step when slicing. What I like about this approach is how naturally it reads. 1. Make a list of the correct length. 2. Populated the even indexes with the contents of list1. 3. Populate the odd indexes with the contents of list2. The fact that the lists are of different lengths is not an issue in this case! – davidchambers Sep 09 '10 at 17:39
  • 2
    I think it only works when len(list1) - len(list2) is 0 or 1. – xan Sep 09 '10 at 19:01
  • 1
    If the lists are of appropriate lengths then it works, if not then the original question doesn't specify what answer is expected. It can be easily modified to handle most reasonable situations: for example if you wanted extra elements to be ignored just chop down the longer list before you start; if you want the extra elements interleaved with None then just make sure that result is initialised with some more None's; if you want extra elements just added on the end then do as for ignoring them and then append them. – Duncan Sep 09 '10 at 19:58
  • Quite right, Duncan; I like your solution. My previous comment was really directed at davidchambers in stating that lengths weren't an issue. Sorry for not being explicit. – xan Sep 09 '10 at 20:39
  • 1
    I, too, was unclear. The point I was trying to make is that Duncan's solution, unlike many of those listed, is not complicated by the fact that the lists are of unequal length. Sure, it's applicable in only a limited range of situations, but I'd prefer a really elegant solution that works in this instance to a less elegant solution that works for any two lists. – davidchambers Sep 09 '10 at 23:48
  • 1
    You can use (2*len(list1)-1) instead of (len(list1)+len(list2)), also i prefer [0::2] instead of [::2]. – Lord British Sep 10 '10 at 00:38
  • +1 works like proposed, but fails if list2 = ['hello'] ValueError: attempt to assign sequence of size 3 to extended slice of size 2 – killown Sep 10 '10 at 19:08
  • That should never occur in this particular case, killown. I'm using re.split() which, when used with a capturing group, always returns a list containing an odd number of items. I'm then processing the "evens" and "odds" separately before combining them into a single list using Duncan's technique. http://bitbucket.org/davidchambers/mango/changeset/6ec62ff16207 – davidchambers Sep 11 '10 at 06:02
  • 1
    This isn't a very pythonic solution: creating the lists in advance can be very wasteful for large inputs, and I think using the total length of the input iters is also unpythonic. There's much cleaner solutions below which produce lazy generators (which can of course easily be coerced to a list as desired). – Dubslow Nov 16 '17 at 12:40
  • 1
    @Dubslow are you the ultimate arbiter of Pythonic? The question asked about combining two lists which is exactly what this answer gives you. The other answers involving iterators already existed when I wrote my answer and I don't believe in duplicating existing answers so I merely set about filling in the gap they had left. For a question such as this there are many good (and Pythonic) answers and no one answer is best in all cases. – Duncan Nov 20 '17 at 11:08
52

There's a recipe for this in the itertools documentation:

from itertools import cycle, islice

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

EDIT:

For python's version greater than 3:

from itertools import cycle, islice

def roundrobin(*iterables):
    "roundrobin('ABC', 'D', 'EF') --> A D E B F C"
    # Recipe credited to George Sakkis
    pending = len(iterables)
    nexts = cycle(iter(it).__next__ for it in iterables)
    while pending:
        try:
            for next in nexts:
                yield next()
        except StopIteration:
            pending -= 1
            nexts = cycle(islice(nexts, pending))
ruddra
  • 40,233
  • 7
  • 54
  • 78
David Z
  • 116,302
  • 26
  • 230
  • 268
  • I find this way more complicated than it needs to be. There's a better option below using `zip_longest`. – Dubslow Nov 16 '17 at 12:39
  • @Dubslow For this particular case, yeah, this is probably overkill (as I mentioned in a comment elsewhere), unless you happen to already have access to it. It might have some advantages in other situations though. This recipe certainly wasn't designed for this problem, it just happens to solve it. – David Z Nov 16 '17 at 19:17
  • 1
    fyi you should use the recipe in the `itertools` [documentation](https://docs.python.org/3/library/itertools.html#itertools-recipes) because `.next()` no longer works. – john w. Jun 02 '20 at 15:21
  • 1
    @johnw. one has to use `__next__`. It isn't written in the documentation so I proposed an edit to the answer. – Marine Galantin Aug 05 '20 at 12:53
  • @Marine I would kind of rather you had just changed the existing code sample, but I can fix that myself. Thanks for contributing! – David Z Aug 05 '20 at 17:53
33
import itertools
print [x for x in itertools.chain.from_iterable(itertools.izip_longest(list1,list2)) if x]

I think this is the most pythonic way of doing it.

user942640
  • 533
  • 4
  • 7
  • 3
    Why this isn't the accepted answer? This is the shortest and most pythonic and works with different list lengths! – Jairo Vadillo Aug 19 '16 at 08:36
  • 5
    the method name is zip_longest not izip_longest – Jairo Vadillo Aug 19 '16 at 08:37
  • 1
    The problem with this is that the default fillin value from zip_longest might overwrite `None`s that are *supposed to be in the list. I'll edit in a tweaked version to fix this – Dubslow Nov 16 '17 at 12:27
  • Note: This will cause problems if the lists contain elements with value `False`, or even things that will only be _evaluated_ as `False` by the `if`-expression, like for example a `0` or an empty list. This can be (partially) avoided by the following: `[x for x in itertools.chain.from_iterable(itertools.zip_longest(list1, list2)) if x is not None]`. Of course, this will still not work if the lists contain `None` elements that need to be preserved. In this case, you need to change the `fillvalue` argument of `zip_longest`, as Dubslow already suggested. – der_herr_g May 14 '19 at 16:35
  • `None` problem seems to be gone, at least since Python 3.7.6 (I don't know for older versions). If `alt_chain` is defined as `def alt_chain(*iters, fillvalue=None): return chain.from_iterable(zip_longest(*iters, fillvalue=fillvalue))`, then `list(alt_chain([0, False, 1, set(), 3, 4], [0, None, 1, {}], fillvalue=99))` correctly returns `[0, 0, False, None, 1, 1, set(), {}, 3, 99, 4, 99]`. – paime Jul 23 '20 at 09:49
31

This should do what you want:

>>> iters = [iter(list1), iter(list2)]
>>> print list(it.next() for it in itertools.cycle(iters))
['f', 'hello', 'o', 'world', 'o']
Mark Byers
  • 719,658
  • 164
  • 1,497
  • 1,412
  • I really liked your initial answer. Though it did not perfectly address the question, it was an elegant way to merge two lists of the same length. I suggest retaining it, along with the lenght caveat, in your current answer. – Paul Sasik Sep 09 '10 at 17:31
  • This is similar to David's answer, but is strictly interleaving (it will stop rather than continue with later lists) – cobbal Sep 09 '10 at 17:41
  • @cobbal: Isn't that what he wanted? – Mark Byers Sep 09 '10 at 17:44
  • This will only work when the lengths of the lists are with 1 of each other. No down vote though as this partial solution is really elegant. – deft_code Sep 09 '10 at 17:47
  • @caspin: *This will only work when the lengths of the lists are with 1 of each other.* What do you mean? It seems to work fine for any lengths. Can you give an example of where it gives a different result from the OPs code? – Mark Byers Sep 09 '10 at 17:48
  • 1
    If list1 were instead ['f', 'o', 'o', 'd'], its final item ('d') would not appear in the resulting list (which is totally fine given the specifics of the question). This is an elegant solution! – davidchambers Sep 09 '10 at 18:08
  • 1
    @Mark yep (I did upvote it), just pointing out the differences (and limitations if other people want different behavior) – cobbal Sep 09 '10 at 18:16
  • 4
    +1 for solving the stated problem, and for doing it simply too :-) I figured something like this would be possible. Honestly I think the `roundrobin` function is sort of overkill for this situation. – David Z Sep 09 '10 at 18:40
  • >>> def rr(*iterables): ... return (i.next() for i in itertools.cycle(map(iter, iterables))) – Zart Sep 10 '10 at 03:20
  • This is a great option, but I've chosen Duncan's approach because of its self-descriptive nature. – davidchambers Sep 11 '10 at 06:08
  • 1
    To work with lists of any size you can simply append what's left in the iterators to the result: `list(itertools.chain(map(next, itertools.cycle(iters)), *iters))` – panda-34 Feb 27 '16 at 19:07
  • This does not work in Python 3.6 `import itertools if __name__ == '__main__': list1 = ['f', 'o', 'o'] list2 = ['hello', 'world'] iters = [iter(list1), iter(list2)] print( list(it.next() for it in itertools.cycle(iters)) )` giving: `AttributeError: 'list_iterator' object has no attribute 'next' – Fernando César Nov 24 '18 at 21:34
20

Without itertools and assuming l1 is 1 item longer than l2:

>>> sum(zip(l1, l2+[0]), ())[:-1]
('f', 'hello', 'o', 'world', 'o')

Using itertools and assuming that lists don't contain None:

>>> filter(None, sum(itertools.izip_longest(l1, l2), ()))
('f', 'hello', 'o', 'world', 'o')
Zart
  • 1,341
  • 9
  • 17
  • This is my favorite answer. It's so concise. – mbomb007 May 18 '17 at 16:25
  • @anishtain4 zip takes pairs of elements as tuples from lists, `[(l1[0], l2[0]), (l1[1], l2[1]), ...]`. `sum` concatenates tuples together: `(l1[0], l2[0]) + (l1[1], l2[1]) + ...` resulting in interleaved lists. Rest of the one-line is just padding of l1 with extra element for zip to work and slice till -1 to get rid of that padding. – Zart May 14 '20 at 05:08
  • izip_longest (zip_longest since python 3) doesn't need +[0] padding, it implicitly fills None when lengths of lists don't match, while `filter(None, ...` (could use `bool` instead, or `None.__ne__`) removes false values, including 0, None and empty strings, so second expression isn't strictly equivalent to first one. – Zart May 14 '20 at 05:14
  • The question is how did you make `sum` do that? What is the role of the second argument there? In the documentations, the second argument is `start`. – anishtain4 May 14 '20 at 14:20
  • Default value of start is 0, and you can't do 0+(some, tuple), thus start is changed to empty tuple. – Zart May 14 '20 at 16:15
13

I know the questions asks about two lists with one having one item more than the other, but I figured I would put this for others who may find this question.

Here is Duncan's solution adapted to work with two lists of different sizes.

list1 = ['f', 'o', 'o', 'b', 'a', 'r']
list2 = ['hello', 'world']
num = min(len(list1), len(list2))
result = [None]*(num*2)
result[::2] = list1[:num]
result[1::2] = list2[:num]
result.extend(list1[num:])
result.extend(list2[num:])
result

This outputs:

['f', 'hello', 'o', 'world', 'o', 'b', 'a', 'r'] 
Community
  • 1
  • 1
mhost
  • 5,700
  • 4
  • 34
  • 43
10

If both lists have equal length, you can do:

[x for y in zip(list1, list2) for x in y]

As the first list has one more element, you can add it post hoc:

[x for y in zip(list1, list2) for x in y] + [list1[-1]]
Someone
  • 2,655
  • 1
  • 21
  • 47
6

Here's a one liner that does it:

list3 = [ item for pair in zip(list1, list2 + [0]) for item in pair][:-1]

Jay
  • 1,243
  • 2
  • 10
  • 9
  • 2
    This works correctly but strikes me as inelegant since it's doing so much to achieve something so simple. I'm not saying that this approach is inefficient, simply that it's not particularly easy to read. – davidchambers Sep 09 '10 at 17:56
2

This one is based on Carlos Valiente's contribution above with an option to alternate groups of multiple items and make sure that all items are present in the output :

A=["a","b","c","d"]
B=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]

def cyclemix(xs, ys, n=1):
    for p in range(0,int((len(ys)+len(xs))/n)):
        for g in range(0,min(len(ys),n)):
            yield ys[0]
            ys.append(ys.pop(0))
        for g in range(0,min(len(xs),n)):
            yield xs[0]
            xs.append(xs.pop(0))

print [x for x in cyclemix(A, B, 3)]

This will interlace lists A and B by groups of 3 values each:

['a', 'b', 'c', 1, 2, 3, 'd', 'a', 'b', 4, 5, 6, 'c', 'd', 'a', 7, 8, 9, 'b', 'c', 'd', 10, 11, 12, 'a', 'b', 'c', 13, 14, 15]
catpnced
  • 21
  • 2
2

Might be a bit late buy yet another python one-liner. This works when the two lists have equal or unequal size. One thing worth nothing is it will modify a and b. If it's an issue, you need to use other solutions.

a = ['f', 'o', 'o']
b = ['hello', 'world']
sum([[a.pop(0), b.pop(0)] for i in range(min(len(a), len(b)))],[])+a+b
['f', 'hello', 'o', 'world', 'o']
Allen
  • 16,421
  • 4
  • 42
  • 54
1

Here's a one liner using list comprehensions, w/o other libraries:

list3 = [sub[i] for i in range(len(list2)) for sub in [list1, list2]] + [list1[-1]]

Here is another approach, if you allow alteration of your initial list1 by side effect:

[list1.insert((i+1)*2-1, list2[i]) for i in range(len(list2))]
chernevik
  • 3,750
  • 9
  • 38
  • 53
1

My take:

a = "hlowrd"
b = "el ol"

def func(xs, ys):
    ys = iter(ys)
    for x in xs:
        yield x
        yield ys.next()

print [x for x in func(a, b)]
Carlos Valiente
  • 872
  • 7
  • 9
1
def combine(list1, list2):
    lst = []
    len1 = len(list1)
    len2 = len(list2)

    for index in range( max(len1, len2) ):
        if index+1 <= len1:
            lst += [list1[index]]

        if index+1 <= len2:
            lst += [list2[index]]

    return lst
killown
  • 3,547
  • 3
  • 22
  • 28
  • Be very careful when using mutable default arguments. This will only return the correct answer the first time it's called, as lst will be reused for every call thereafter. This would be better written as lst=None ... if lst is None: lst = [], although I don't see a compelling reason to opt for this approach over others listed here. – davidchambers Sep 09 '10 at 23:55
  • lst is defined within the function so is a local variable. The potential problem is that list1 and list2 are will be reused every time you use the function, even if you call the function with different lists. See http://docs.python.org/tutorial/controlflow.html#default-argument-values – blokeley Sep 10 '10 at 07:59
  • 1
    @blokeley: wrong, would be reused if it was combine(list1=[...], list2=[...]) – killown Sep 10 '10 at 18:29
  • When this solution was first posted its first line read `def combine(list1, list2, lst=[]):`, hence my comment. By the time I submitted that comment, though, killown had made the necessary change. – davidchambers Sep 11 '10 at 15:33
1
from itertools import chain
list(chain(*zip('abc', 'def')))  # Note: this only works for lists of equal length
['a', 'd', 'b', 'e', 'c', 'f']
GeneralCode
  • 444
  • 3
  • 13
Ken Seehart
  • 154
  • 1
  • 7
0

This is nasty but works no matter the size of the lists:

list3 = [element for element in list(itertools.chain.from_iterable([val for val in itertools.izip_longest(list1, list2)])) if element != None]
0

Stops on the shortest:

def interlace(*iters, next = next) -> collections.Iterable:
    """
    interlace(i1, i2, ..., in) -> (
        i1-0, i2-0, ..., in-0,
        i1-1, i2-1, ..., in-1,
        .
        .
        .
        i1-n, i2-n, ..., in-n,
    )
    """
    return map(next, cycle([iter(x) for x in iters]))

Sure, resolving the next/__next__ method may be faster.

jwp
  • 396
  • 3
  • 10
0

Multiple one-liners inspired by answers to another question:

import itertools

list(itertools.chain.from_iterable(itertools.izip_longest(list1, list2, fillvalue=object)))[:-1]

[i for l in itertools.izip_longest(list1, list2, fillvalue=object) for i in l if i is not object]

[item for sublist in map(None, list1, list2) for item in sublist][:-1]
Community
  • 1
  • 1
wordsforthewise
  • 8,361
  • 3
  • 56
  • 90
0

How about numpy? It works with strings as well:

import numpy as np

np.array([[a,b] for a,b in zip([1,2,3],[2,3,4,5,6])]).ravel()

Result:

array([1, 2, 2, 3, 3, 4])
Nikolay Frick
  • 1,913
  • 19
  • 16
0

An alternative in a functional & immutable way (Python 3):

from itertools import zip_longest
from functools import reduce

reduce(lambda lst, zipped: [*lst, *zipped] if zipped[1] != None else [*lst, zipped[0]], zip_longest(list1, list2),[])
godot
  • 1,205
  • 12
  • 27
-1

I'd do the simple:

chain.from_iterable( izip( list1, list2 ) )

It'll come up with an iterator without creating any additional storage needs.

wheaties
  • 34,173
  • 12
  • 82
  • 126
  • 1
    It's really simple, but it only works with lists of the same length! – Jochen Ritzel Sep 09 '10 at 17:31
  • You can fix it with `chain.from_iterable(izip(list1, list2), list1[len(list2):])` for the particular problem asked here ... list1 is supposed to be the longer one. – Jochen Ritzel Sep 09 '10 at 17:37
  • Yeah but I'd rather come up with either a solution that works for arbitrary length containers or yield to the solutions proposed above. – wheaties Sep 09 '10 at 17:40
-2

I'm too old to be down with list comprehensions, so:

import operator
list3 = reduce(operator.add, zip(list1, list2))
Tom Anderson
  • 42,965
  • 15
  • 81
  • 123