12

I have two lists of length n and n+1:

[a_1, a_2, ..., a_n]
[b_1, b_2, ..., b_(n+1)]

I want a function giving as a result a list with alternate elements from the two, that is

[b_1, a_1, ..., b_n, a_n, b_(n+1)]

The following works, but does not look smart:

def list_mixing(list_long,list_short):
    list_res = []
    for i in range(len(list_short)):
        list_res.extend([list_long[i], list_short[i]])
    list_res.append(list_long[-1])
    return list_res

Can anyone suggest a more pythonic way of doing this? Thanks!

Giulia
  • 297
  • 1
  • 2
  • 11

12 Answers12

18
>>> import itertools
>>> a
['1', '2', '3', '4', '5', '6']
>>> b
['a', 'b', 'c', 'd', 'e', 'f']
>>> list(itertools.chain.from_iterable(zip(a,b)))
['1', 'a', '2', 'b', '3', 'c', '4', 'd', '5', 'e', '6', 'f']

zip() produces a iterable with the length of shortest argument. You can either append a[-1] to the result, or use itertools.zip_longest(izip_longest for Python 2.x) with a fill value and delete that value afterwards.

And you can use more than two input sequences with this solution.

For not appending the last value, you can try this dirty approach, but I don't really recommend it, it isn't clear:

>>> a
[1, 2, 3, 4, 5]
>>> b
['a', 'b', 'c', 'd', 'e', 'f']
>>> [a[i//2] if i%2 else b[i//2] for i in range(len(a)*2+1)]
['a', 1, 'b', 2, 'c', 3, 'd', 4, 'e', 5, 'f']

(For Python 2.x, use single /)

utdemir
  • 26,532
  • 10
  • 62
  • 81
14

IMHO the best way is:

result = [item for sublist in zip(a,b) for item in sublist]

It's also faster than sum and reduce ways.

UPD Sorry missed that your second list is bigger by one element :) There is another crazy way:

result = [item for sublist in map(None, a, b) for item in sublist][:-1]
Mikhail Churbanov
  • 4,436
  • 1
  • 28
  • 36
4

more-itertools has roundrobin which exactly does the job:

from more_itertools import roundrobin

l1 = [1, 3, 5]
l2 = [2, 4, 6, 8, 10]

print(list(roundrobin(l1,l2)))
# [1, 2, 1, 3, 4, 3, 5, 6, 5, 8, 10]
wrznr
  • 91
  • 4
4
>>> long = [1, 3, 5, 7]
>>> short = [2, 4, 6]
>>> mixed = []
>>> for i in range(len(long)):
>>>     mixed.append(long[i])
>>>     if i < len(short)
>>>         mixed.append(short[i])
>>> mixed
[1, 2, 3, 4, 5, 6, 7]
Jin
  • 12,748
  • 3
  • 36
  • 41
3

mixing two lists is a job for zip:

res = []
for a,b in zip(list_long, list_short):
    res += [a,b]

for lists of differing lengths, define your own function:

def mix(list_long, list_short):
    result = []
    i,j = iter(list_long), iter(list_short)
    for a,b in zip(i,j):
        res += [a,b]
    for rest in i:
        result += rest
    for rest in j:
        result += rest
    return result

using the answer given by Mihail, we can shorten this to:

def mix(list_long, list_short):
    i,j = iter(list_long), iter(list_short)
    result = [item for sublist in zip(i,j) for item in sublist]
    result += [item for item in i]
    result += [item for item in j]
    return result
Community
  • 1
  • 1
Adrien Plisson
  • 22,486
  • 6
  • 42
  • 73
  • The resulting list will be truncated in length so you'll miss the remaining elements in the longer list. –  Sep 23 '11 at 13:10
  • Good approach, but be careful: This works only, if both lists have the same length (zip truncates the longer of both lists). – phimuemue Sep 23 '11 at 13:10
3

I would use a combination of the above answers:

>>> a = ['1', '2', '3', '4', '5', '6']

>>> b = ['a', 'b', 'c', 'd', 'e', 'f', 'g']

>>> [i for l in izip_longest(a, b, fillvalue=object) for i in l if i is not object]
<<< ['1', 'a', '2', 'b', '3', 'c', '4', 'd', '5', 'e', '6', 'f', 'g']
Zach Kelling
  • 52,505
  • 13
  • 109
  • 108
1

Use izip_longest filling the gaps with something you don't have in your lists, or don't want to keep in the result. If you don't want to keep anything resolving to False, use the defaults for izip_longest and filter:

from itertools import chain, izip_longest
l1 = [1,3,5]
l2 = [2,4,6,8,10]
filter(None, chain(*izip_longest(l1,l2)))

the result is: [1, 2, 3, 4, 5, 6, 8, 10]

Using None for filling the gaps and removing them with filter:

filter(lambda x: x is not None, chain(*izip_longest(l1,l2, fillvalue=None)))

For better efficiency, when l1 or l2 are not short lists but e.g. very long or infinite iterables, instead of filter use ifilter, which will give you an iterable instead of putting all in memory in a list. Example:

from itertools import chain, izip_longest, ifilter
for value in ifilter(None, chain(*izip_longest(iter1,iter1))):
    print value
Ivan Ogai
  • 1,406
  • 15
  • 9
  • In the first code block, you should only be importing itertools, you don't need the chain, izip_longest functions because you're not using them directly in that block... – user772401 Sep 10 '14 at 22:17
  • Use a sentinel object() instead of None, but otherwise what I came up with is pretty similar. – Dubslow Nov 16 '17 at 12:23
1

Another answer for different lengths and more lists using zip_longest and filtering out None elements at the end

from itertools import zip_longest
a = [1, 2, 3]
b = ['a', 'b', 'c', 'd']   
c = ['A', 'B', 'C', 'D', 'E']    
[item for sublist in zip_longest(*[a,b,c]) for item in sublist if item]

returns

[1, 'a', 'A', 2, 'b', 'B', 3, 'c', 'C', 'd', 'D', 'E']
Erik Sillén
  • 337
  • 2
  • 13
  • Basically the same as [this answer](https://stackoverflow.com/a/7529841/6243352), but less generalized because it uses `None`. No need for the `*[]` wrapper in the parameter list unless they're already packed; `zip_longest(a, b, c)` works. – ggorlen Sep 04 '21 at 21:05
0

This is the best I've found:

import itertools

l1 = [1, 3, 5]
l2 = [2, 4, 6, 8, 10]

result = [
    x # do something
    for x in itertools.chain.from_iterable(itertools.zip_longest(l1, l2, l1))
    if x is not None
]
result
# [1, 2, 1, 3, 4, 3, 5, 6, 5, 8, 10]

To make it extra clear, zip_longest groups the elements together per index:

iter = list(itertools.zip_longest(l1, l2, l1))
iter[0]
# (1, 2, 1)
iter[1]
# (3, 4, 3)
iter[-1] # last
# (None, 10, None)

After, itertools.chain.from_iterable flattens them in order.

The reasons why it is the best:

  • filtering None is not recommended anymore, use a list comprehension
  • a list comprehension also allows you to immediately "do something" with x
  • it does not throw away items when some lists are longer than the shortest one
  • it works with any amount of lists
  • it is actually super easy to reason what is happening contrary to all the "cleverness" out there
PascalVKooten
  • 20,643
  • 17
  • 103
  • 160
0

You could do something like the following (assuming len(list_long)==len(list_short)+1:

def list_mixing(list_long,list_short):
    return [(list_long[i/2] if i%2==0 else list_short[i/2]) for i in range(len(list_long)+len(list_short)]

Where I am using / for integer division (exactly what the operator is for that depends on the language version).

murgatroid99
  • 19,007
  • 10
  • 60
  • 95
0
sum([[x,y] for x,y in zip(b,a)],[])+[b[-1]]

Note: This works only for your given list lengths, but can easily be extended to arbitrary length lists.

phimuemue
  • 34,669
  • 9
  • 84
  • 115
  • 1
    Using sum for concatenating is generally discouraged: http://docs.python.org/library/functions.html#sum – utdemir Sep 23 '11 at 13:16
  • @utdemir: Using `sum` for concatening strings is definitely discouraged -- good thing phimuemue is using it to append to a list. ;) – Ethan Furman Sep 23 '11 at 21:27
  • 1
    He is concentening lists, from the link i gave: "To concatenate a series of iterables, consider using itertools.chain()." – utdemir Sep 23 '11 at 22:13
  • This solution is unnecessarily building new lists for every element due to abuse of `sum`. It's quadratic. – ggorlen Sep 04 '21 at 21:04
-1

Use zip. That will give you a list of tuples, like: [('a_1', 'b_1'), ('a_2', 'b_2'), ('a_3', 'b_3')]

If you want to clean that up into a nice list, just iterate over the list of tuples with enumerate:

alist = ['a_1', 'a_2', 'a_3']
blist = ['b_1', 'b_2', 'b_3']
clist = []

for i, (a, b) in enumerate(zip(alist, blist)):
    clist.append(a)
    clist.append(b)
print clist
['a_1', 'b_1', 'a_2', 'b_2', 'a_3', 'b_3']
Canuteson
  • 598
  • 1
  • 4
  • 11