2010-09-09 2 views
52

У меня есть два списка, , первый из которых, как гарантируется, содержит ровно еще один пункт, чем второй. Я хотел бы знать самый Pythonic способ создать новый список, чьи значения с четным индексом исходят из первого списка, а значения нечетного индекса - из второго списка.Pythonic способ объединить два списка в чередующемся режиме?

# example inputs 
list1 = ['f', 'o', 'o'] 
list2 = ['hello', 'world'] 

# desired output 
['f', 'hello', 'o', 'world', 'o'] 

Это работает, но не очень:

list3 = [] 
while True: 
    try: 
     list3.append(list1.pop(0)) 
     list3.append(list2.pop(0)) 
    except IndexError: 
     break 

Как еще это может быть достигнуто? Какой самый питонический подход?

+1

возможно дубликат [Чередование итераторы в Python] (http://stackoverflow.com/questions/2017895/alternating-between-iterators-in-python) –

+0

Не дубликат! В принятом ответе в вышеприведенной статье создается список кортежей, а не один объединенный список. –

+0

@Paul: Да, принятый ответ не дает полного решения. Прочтите комментарии и другие ответы. Вопрос в основном тот же, и другие решения могут быть применены здесь. –

ответ

66

Вот один из способов сделать это нарезка:

>>> list1 = ['f', 'o', 'o'] 
>>> list2 = ['hello', 'world'] 
>>> result = [None]*(len(list1)+len(list2)) 
>>> result[::2] = list1 
>>> result[1::2] = list2 
>>> result 
['f', 'hello', 'o', 'world', 'o'] 
+2

Спасибо, Дункан. Я не понимал, что можно указать шаг при нарезке. То, что мне нравится в этом подходе, - это то, как естественно это читается. 1. Составьте список правильной длины. 2. Настроить четные индексы с содержимым списка1. 3. Заполните нечетные индексы содержимым списка2. Тот факт, что списки имеют разную длину, не является проблемой в этом случае! – davidchambers

+1

Я думаю, что это работает только тогда, когда len (list1) - len (list2) равно 0 или 1. – xan

+0

Если списки имеют соответствующую длину, то это работает, если нет, то исходный вопрос не определяет, какой ответ ожидается. Его можно легко модифицировать для обработки наиболее разумных ситуаций: например, если вы хотите, чтобы лишние элементы игнорировались, просто отрубите более длинный список перед запуском; если вы хотите, чтобы дополнительные элементы чередовали с None, просто убедитесь, что результат инициализирован еще несколькими None; если вы хотите добавить дополнительные элементы в конец, тогда сделайте так, чтобы их игнорировать, а затем добавить их. – Duncan

44

Там рецепт для этого в itertools documentation:

from itertools import cycle, islice 

def roundrobin(*iterables): 
    "roundrobin('ABC', 'D', 'EF') --> A D E B F C" 
    # Recipe credited to George Sakkis 
    pending = len(iterables) 
    nexts = cycle(iter(it).next for it in iterables) 
    while pending: 
     try: 
      for next in nexts: 
       yield next() 
     except StopIteration: 
      pending -= 1 
      nexts = cycle(islice(nexts, pending)) 
+18

Есть ли что-нибудь, что itertools не может сделать? +1. –

+21

@Manoj полет. Вам нужен модуль антигравитации для этого – cobbal

+0

. Я считаю этот способ более сложным, чем нужно. Ниже приведена более удобная опция 'zip_longest'. – Dubslow

24

Это должно делать то, что вы хотите:

>>> iters = [iter(list1), iter(list2)] 
>>> print list(it.next() for it in itertools.cycle(iters)) 
['f', 'hello', 'o', 'world', 'o'] 
+0

Мне очень понравился ваш первоначальный ответ. Несмотря на то, что вопрос не был полностью решен, это был элегантный способ объединить два списка одинаковой длины. Я предлагаю сохранить его вместе с длинным оговоркой в ​​вашем текущем ответе. –

+0

Это похоже на ответ Дэвида, но строго чередуется (это остановится, а не продолжит работу с более поздними списками) – cobbal

+0

@cobbal: Разве это не то, что он хотел? –

-2

Я бы сделал простой:

chain.from_iterable(izip(list1, list2)) 

Он будет иметь итератор без каких-либо дополнительных потребностей в хранении.

+0

Это не работает. Он дает '['f', 'hello', 'o', 'world']'. –

+1

Это действительно просто, но он работает только со списками одинаковой длины! –

+0

Итак, вы правы, для этого нужны контейнеры с одинаковой длиной. Hrm ... – wheaties

-2

Я слишком стар, чтобы быть вниз с списковых, так:

import operator 
list3 = reduce(operator.add, zip(list1, list2)) 
+1

Это дает ('f', 'hello', 'o', 'world'), что неверно. – davidchambers

3

Вот один вкладыш, который делает это:

list3 = [ item for pair in zip(list1, list2 + [0]) for item in pair][:-1]

+1

Это работает правильно, но поражает меня как неэлегантного, так как он делает так много, чтобы достичь чего-то такого простого. Я не говорю, что этот подход неэффективен, просто его читать не очень легко. – davidchambers

0

Вот один вкладыш с использованием списковых, без других библиотек:

list3 = [sub[i] for i in range(len(list2)) for sub in [list1, list2]] + [list1[-1]] 

Вот еще один подход, если вы разрешите lteration вашего первоначального list1 по побочному эффекту:

[list1.insert((i+1)*2-1, list2[i]) for i in range(len(list2))] 
1

Моего мнение:

a = "hlowrd" 
b = "el ol" 

def func(xs, ys): 
    ys = iter(ys) 
    for x in xs: 
     yield x 
     yield ys.next() 

print [x for x in func(a, b)] 
0

Остановки на кратчайшем:

def interlace(*iters, next = next) -> collections.Iterable: 
    """ 
    interlace(i1, i2, ..., in) -> (
     i1-0, i2-0, ..., in-0, 
     i1-1, i2-1, ..., in-1, 
     . 
     . 
     . 
     i1-n, i2-n, ..., in-n, 
    ) 
    """ 
    return map(next, cycle([iter(x) for x in iters])) 

Конечно, решение следующего/__ next__ метода может быть быстрее.

1
def combine(list1, list2): 
    lst = [] 
    len1 = len(list1) 
    len2 = len(list2) 

    for index in range(max(len1, len2)): 
     if index+1 <= len1: 
      lst += [list1[index]] 

     if index+1 <= len2: 
      lst += [list2[index]] 

    return lst 
+0

Будьте очень осторожны при использовании изменяемых аргументов по умолчанию. Это вернет правильный ответ при первом вызове, так как lst будет повторно использоваться для каждого вызова после этого. Это было бы лучше написано как lst = None ... if lst is None: lst = [], хотя я не вижу веских оснований выбирать этот подход по сравнению с другими, перечисленными здесь. – davidchambers

+0

lst находится внутри функции ... – killown

+0

lst определяется внутри функции, так что это локальная переменная. Потенциальная проблема заключается в том, что list1 и list2 будут использоваться повторно каждый раз, когда вы используете эту функцию, даже если вы вызываете функцию с разными списками. См. Http://docs.python.org/tutorial/controlflow.html#default-argument-values ​​ – blokeley

8

Без itertools и предполагая l1 1 пункт больше, чем l2:

>>> sum(zip(l1, l2+[0]),())[:-1] 
('f', 'hello', 'o', 'world', 'o') 

Использование itertools и предполагая, что списки не содержат None:

>>> filter(None, sum(itertools.izip_longest(l1, l2),())) 
('f', 'hello', 'o', 'world', 'o') 
+0

Отличные лайнеры, особенно первые ... без каких-либо 'itertools', это победитель для меня! – MestreLion

+0

Это мой любимый ответ. Это так лаконично. – mbomb007

2

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

A=["a","b","c","d"] 
B=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] 

def cyclemix(xs, ys, n=1): 
    for p in range(0,int((len(ys)+len(xs))/n)): 
     for g in range(0,min(len(ys),n)): 
      yield ys[0] 
      ys.append(ys.pop(0)) 
     for g in range(0,min(len(xs),n)): 
      yield xs[0] 
      xs.append(xs.pop(0)) 

print [x for x in cyclemix(A, B, 3)] 

Это чересстрочной списки А и Б по группам из 3 значений каждого:

['a', 'b', 'c', 1, 2, 3, 'd', 'a', 'b', 4, 5, 6, 'c', 'd', 'a', 7, 8, 9, 'b', 'c', 'd', 10, 11, 12, 'a', 'b', 'c', 13, 14, 15] 
7

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

Здесь Duncan's solution приспособлен для работы с двумя списками разного размера.

list1 = ['f', 'o', 'o', 'b', 'a', 'r'] 
list2 = ['hello', 'world'] 
num = min(len(list1), len(list2)) 
result = [None]*(num*2) 
result[::2] = list1[:num] 
result[1::2] = list2[:num] 
result.extend(list1[num:]) 
result.extend(list2[num:]) 
result 

Это выходы:

['f', 'hello', 'o', 'world', 'o', 'b', 'a', 'r'] 
0

Это противно, но работает независимо от размера списков:

list3 = [element for element in list(itertools.chain.from_iterable([val for val in itertools.izip_longest(list1, list2)])) if element != None] 
15
import itertools 
print [x for x in itertools.chain.from_iterable(itertools.izip_longest(list1,list2)) if x] 

Я думаю, это самый вещий способ сделать Это.

+3

Почему это не принятый ответ? Это самый короткий и самый pythonic и работает с разной длиной списка! –

+1

имя метода zip_longest не izip_longest –

+3

Гораздо лучше, чем принятый ответ! – Vinay87

0

Несколько однострочечников вдохновленных answers to another question:

import itertools 

list(itertools.chain.from_iterable(itertools.izip_longest(list1, list2, fillvalue=object)))[:-1] 

[i for l in itertools.izip_longest(list1, list2, fillvalue=object) for i in l if i is not object] 

[item for sublist in map(None, list1, list2) for item in sublist][:-1] 
Смежные вопросы