Как изящно чередовать два списка неравномерной длины в python?
Я хочу объединить два списка в python, причем списки имеют разную длину, так что элементы более короткого списка как можно более равномерно распределены в конечном списке. т.е. я хочу взять [1, 2, 3, 4]
и ['a','b']
и объединить их, чтобы получить список, похожий на [1, 'a', 2, 3, 'b', 4]
. Он должен иметь возможность работать со списками, которые также не являются точными кратными, поэтому он может принимать [1, 2, 3, 4, 5]
и ['a', 'b', 'c']
и производить [1, 'a', 2, 'b', 3, 'c', 4, 5]
или аналогичный. Он должен сохранять упорядочение обоих списков.
Я могу понять, как это сделать с помощью метода перебора грубой силы, но поскольку Python, похоже, обладает огромным набором превосходных инструментов для выполнения всех видов умных вещей, о которых я не знаю (пока), я задавался вопросом, есть что-нибудь более элегантное, которое я могу использовать?
NB: Я использую Python 3.3.
Ответы
Ответ 1
если a
- более длинный список, а b
- более короткий
from itertools import groupby
len_ab = len(a) + len(b)
groups = groupby(((a[len(a)*i//len_ab], b[len(b)*i//len_ab]) for i in range(len_ab)),
key=lambda x:x[0])
[j[i] for k,g in groups for i,j in enumerate(g)]
например,
>>> a = range(8)
>>> b = list("abc")
>>> len_ab = len(a) + len(b)
>>> groups = groupby(((a[len(a)*i//len_ab], b[len(b)*i//len_ab]) for i in range(len_ab)), key=lambda x:x[0])
>>> [j[i] for k,g in groups for i,j in enumerate(g)]
[0, 'a', 1, 2, 'b', 3, 4, 5, 'c', 6, 7]
Вы можете использовать этот трюк, чтобы убедиться, что a
длиннее b
b, a = sorted((a, b), key=len)
Ответ 2
Это в основном то же, что алгоритм линии Bresenham. Вы можете вычислить позиции "пикселя" и использовать их в качестве индексов в списках.
Если ваша задача отличается тем, что вы хотите, чтобы каждый элемент отображался один раз. Вам нужно либо изменить алгоритм, либо выполнить обработку индексов, добавив элементы из списков только в первый раз, когда они появятся. Однако есть небольшая двусмысленность: когда оба индекса пикселя/списка изменяются одновременно, вам нужно выбрать, какой из них следует включить первым. Это соответствует двум различным вариантам для чередования списков, упомянутых в вопросе и комментариях.
Ответ 3
В значительной степени заимствуя решение Джона Клемента, вы можете написать функцию, которая принимает произвольное количество последовательностей и возвращает объединенную последовательность равномерно расположенных объектов:
import itertools as IT
def evenly_spaced(*iterables):
"""
>>> evenly_spaced(range(10), list('abc'))
[0, 1, 'a', 2, 3, 4, 'b', 5, 6, 7, 'c', 8, 9]
"""
return [item[1] for item in
sorted(IT.chain.from_iterable(
zip(IT.count(start=1.0 / (len(seq) + 1),
step=1.0 / (len(seq) + 1)), seq)
for seq in iterables))]
iterables = [
['X']*2,
range(1, 11),
['a']*3
]
print(evenly_spaced(*iterables))
дает
[1, 2, 'a', 3, 'X', 4, 5, 'a', 6, 7, 'X', 8, 'a', 9, 10]
Ответ 4
С предположением, что a
- это последовательность, которая должна быть вставлена в:
from itertools import izip, count
from operator import itemgetter
import heapq
a = [1, 2, 3, 4]
b = ['a', 'b']
fst = enumerate(a)
snd = izip(count(0, len(a) // len(b)), b)
print map(itemgetter(1), heapq.merge(fst, snd))
# [1, 'a', 2, 3, 'b', 4]
Ответ 5
Если мы изменим @Jon ответ так:
from itertools import count
import heapq
[x[1] for x in heapq.merge(izip(count(0, len(b)), a), izip(count(0, len(a)), b))]
Не имеет значения, какой из a
/b
длиннее
Ответ 6
Если мы хотим сделать это без itertools:
def interleave(l1, l2, default=None):
max_l = max(len(l1), len(l2))
data = map(lambda x: x + [default] * (max_l - len(x)), [l1,l2])
return [data[i%2][i/2] for i in xrange(2*max_l)]
Ahh, пропустил равноотстоящую часть. Это по какой-то причине было помечено как дубликат с вопросом, который не требовал равномерного разделения при наличии различной длины списка.
Ответ 7
# Given
a = [1, 2, 3, 4]
b = ['a','b']
c = [1, 2, 3, 4, 5]
d = ['a', 'b', 'c']
Вы можете чередоваться через самый длинный истребитель:
import itertools as it
list(i for i in it.chain(*it.zip_longest(a, b)) if i is not None)
# [1, 'a', 2, 'b', 3, 4]
list(i for i in it.chain(*it.zip_longest(c, d)) if i is not None)
# [1, 'a', 2, 'b', 3, 'c', 4, 5]
Также рассмотрите возможность установки more_itertools
, который поставляется с interleave_longest
и interleave
.
import more_itertools
list(more_itertools.interleave_longest(a, b))
# [1, 'a', 2, 'b', 3, 4]
list(more_itertools.interleave_longest(c, d))
# [1, 'a', 2, 'b', 3, 'c', 4, 5]