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