Pythonic способ смешать два списка

У меня есть два списка длины n и n+1:

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

Я хочу функцию, дающую в результате список с альтернативными элементами из двух, то есть

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

Следующее работает, но не выглядит умным:

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

Кто-нибудь может предложить более питонический способ сделать это? Спасибо!

12 ответов

Решение

ИМХО лучший способ это:

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

Это также быстрее, чем сумма и уменьшить пути.

UPD Извините, пропустил, что ваш второй список больше на один элемент:) Есть еще один сумасшедший способ:

result = [item for sublist in map(None, a, b) for item in sublist][:-1]
>>> 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() производит итерацию с длиной кратчайшего аргумента. Вы можете добавить a[-1] к результату или использовать itertools.zip_longest(izip_longest для Python 2.x) со значением заполнения и впоследствии удалите это значение.

И вы можете использовать более двух входных последовательностей с этим решением.

Чтобы не добавлять последнее значение, вы можете попробовать этот грязный подход, но я не очень рекомендую его, это не ясно:

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

(Для Python 2.x используйте одиночный /)

В more-itertools есть roundrobin, который выполняет свою работу:

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

Я бы использовал комбинацию вышеуказанных ответов:

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

Смешивание двух списков - это работа для zip:

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

для списков разной длины определите свою собственную функцию:

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

используя ответ, данный Михаилом, мы можем сократить его до:

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

Использование izip_longest заполнение пробелов чем-то, чего у вас нет в ваших списках, или вы не хотите сохранять результат. Если вы не хотите, чтобы что-то разрешало Falseиспользуйте значения по умолчанию для izip_longest а также filter:

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

результат: [1, 2, 3, 4, 5, 6, 8, 10]

С помощью None для заполнения пробелов и удаления их filter:

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

Для повышения эффективности, когда l1 или же l2 не короткие списки, а, например, очень длинные или бесконечные итерируемые, вместо filter использование ifilter, который даст вам итерацию вместо того, чтобы поместить все в память в списке. Пример:

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

Примечание. Это работает только для заданных длин списков, но может быть легко расширено до списков произвольной длины.

Другой ответ для разной длины и большего количества списков с использованием zip_longest и фильтрации элементов None в конце

      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]

возвращается

      [1, 'a', 'A', 2, 'b', 'B', 3, 'c', 'C', 'd', 'D', 'E']

Это лучшее, что я нашел:

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]

Чтобы сделать это более ясным, zip_longest группирует элементы вместе по индексу:

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

После, itertools.chain.from_iterable выравнивает их по порядку.

Причины, почему он лучший:

  • фильтрация None больше не рекомендуется, используйте понимание списка
  • понимание списка также позволяет немедленно "сделать что-то" с помощью x
  • он не выбрасывает элементы, если некоторые списки длиннее самого короткого
  • работает с любым количеством списков
  • на самом деле очень легко рассуждать о том, что происходит вопреки всей "умности" там

Вы могли бы сделать что-то вроде следующего (предполагая, 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)]

Где я использую / для целочисленного деления (от чего зависит оператор, зависит от языковой версии).

Используйте почтовый индекс. Это даст вам список кортежей, например: [('a_1', 'b_1'), ('a_2', 'b_2'), ('a_3', 'b_3')]

Если вы хотите убрать это в хороший список, просто переберите список кортежей с помощью 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']
Другие вопросы по тегам