开发者

Pythonic way to mix two lists [duplicate]

This question already has answers here: Pythonic way to combine (interleave, interlace, intertwine) two lists in an alternating fashion? (26 answers) Closed 8 months ago.

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


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]


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


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


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


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.


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


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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜