简体   繁体   中英

Pythonic way to mix two lists

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!

>>> 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 / )

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]
>>> 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]

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

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']

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 eg 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
sum([[x,y] for x,y in zip(b,a)],[])+[b[-1]]

注意:这仅适用于给定的列表长度,但可以轻松扩展到任意长度的列表。

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

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]

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).

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']

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM