Как изящно чередовать два списка неравномерной длины в 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]