2008-10-28 5 views
18

У меня есть два итератора, list и объект itertools.count (т. Е. Генератор бесконечного значения). Я хотел бы объединить эти два в результате итератор, который будет чередовать значения доходности между ними:Как слить два итератора python?

>>> import itertools 
>>> c = itertools.count(1) 
>>> items = ['foo', 'bar'] 
>>> merged = imerge(items, c) # the mythical "imerge" 
>>> merged.next() 
'foo' 
>>> merged.next() 
1 
>>> merged.next() 
'bar' 
>>> merged.next() 
2 
>>> merged.next() 
Traceback (most recent call last): 
    ... 
StopIteration 

Что является самым простым, самым кратким способом сделать это?

+0

Не использовать это люди: `список ((выход рядом (с)) или я для г в пунктах) ` – 2017-07-12 12:21:45

ответ

36

Генератор решит вашу проблему красиво.

def imerge(a, b): 
    for i, j in itertools.izip(a,b): 
     yield i 
     yield j 
+10

Вы должны добавить отказ от ответственности - это будет работать, только если список a конечен. – Claudiu 2008-10-28 16:17:20

+0

импорт itertools Защиту Imerge (а, б): для I, J в почтовом индексе (а, б): выхода я выход J с = itertools.count (1) товара = [ 'Foo', 'bar'] для i in imerge (c, items): print i Я пробую это, и это все еще работает. len (zipped list) = min (l1, l2). Поэтому ограничение конечной длины не требуется. – Pramod 2008-10-28 16:22:58

+2

Claudiu - это правильно. Попробуйте закрепить два бесконечных генератора - в конечном итоге у вас закончится память. Я бы предпочел использовать itertools.izip вместо zip. Затем вы строите почтовый индекс, когда вы идете, а не сразу. Вы все равно должны следить за бесконечными циклами, но эй. – 2008-10-28 16:40:28

10

Я бы сделал что-то подобное. Это будет наиболее эффективным временем и пространством, так как у вас не будет накладных расходов на скрепление объектов. Это также будет работать, если оба a и b бесконечны.

def imerge(a, b): 
    i1 = iter(a) 
    i2 = iter(b) 
    while True: 
     try: 
      yield i1.next() 
      yield i2.next() 
     except StopIteration: 
      return 
+0

Ошибка/исключение здесь прерывает протокол итератора, заглушая StopIteration, не так ли? – 2008-10-28 16:46:17

+0

@ David Eyk: все в порядке, потому что возвращение от генератора все равно вызывает StopIteration. Утверждение try в этом случае является излишним. – efotinis 2008-10-28 18:00:24

+0

@efotinis: Я этого не знал. Благодаря! – 2008-10-29 03:48:39

7

Вы можете использовать zip, а также itertools.chain. Это будет работать только , если первый список конечное:

merge=itertools.chain(*[iter(i) for i in zip(['foo', 'bar'], itertools.count(1))]) 
15

Вы можете сделать что-то, что почти exaclty, что @Pramod первым предложил.

def izipmerge(a, b): 
    for i, j in itertools.izip(a,b): 
    yield i 
    yield j 

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

0

Для чего нужен itertools?

def imerge(a,b): 
    for i,j in zip(a,b): 
     yield i 
     yield j 

В этом случае, по меньшей мере, один из А или Б должна быть конечной длины, потому что застежка-молния будет возвращать список, а не итератора. Если вам нужен итератор в качестве вывода, вы можете пойти на решение Claudiu.

3

Я не уверен, что ваше приложение, но вы можете найти функцию enumerate() более полезной.

>>> items = ['foo', 'bar', 'baz'] 
>>> for i, item in enumerate(items): 
... print item 
... print i 
... 
foo 
0 
bar 
1 
baz 
2 
9

Я также согласен с тем, что itertools не требуется.

Но зачем останавливаться на 2?

def tmerge(*iterators): 
    for values in zip(*iterators): 
     for value in values: 
     yield value 

обрабатывает любое количество итераторов от 0 до.

ОБНОВЛЕНИЕ: DOH! Один из комментаторов отметил, что это не сработает, если все итераторы не будут иметь одинаковую длину.

Правильный код:

def tmerge(*iterators): 
    empty = {} 
    for values in itertools.izip_longest(*iterators, fillvalue=empty): 
    for value in values: 
     if value is not empty: 
     yield value 

и да, я просто попытался его со списками разной длины, а также список, содержащий {}.

0

Использование itertools.izip(), вместо того, чтобы молнии(), как и в некоторых других ответов, улучшит производительность:

Как «pydoc itertools.izip» показывает: «Работает как функции почтового индекса(), но потребляет меньше памяти, возвращая итератор вместо списка ».

Itertools.izip также будет работать правильно, даже если один из итераторов бесконечен.

1

Используйте izip и цепь вместе:

>>> list(itertools.chain.from_iterable(itertools.izip(items, c))) # 2.6 only 
['foo', 1, 'bar', 2] 

>>> list(itertools.chain(*itertools.izip(items, c))) 
['foo', 1, 'bar', 2] 
0

Краткий способ заключается в использовании выражения генератора с itertools.cycle(). Он избегает создания длинной цепочки() кортежей.

generator = (it.next() for it in itertools.cycle([i1, i2])) 
3

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

iter = reduce(lambda x,y: itertools.chain(x,y), iters) 
3

Одним из менее известных особенностей Python является то, что вы можете иметь больше для пунктов в выражении генератора. Очень полезно для сглаживания вложенных списков, например, из zip()/izip().

def imerge(*iterators): 
    return (value for row in itertools.izip(*iterators) for value in row) 
3

Вот элегантное решение:

def alternate(*iterators): 
    while len(iterators) > 0: 
     try: 
      yield next(iterators[0]) 
      # Move this iterator to the back of the queue 
      iterators = iterators[1:] + iterators[:1] 
     except StopIteration: 
      # Remove this iterator from the queue completely 
      iterators = iterators[1:] 

Использование фактической очереди для лучшей производительности (как это было предложено Давида):

from collections import deque 

def alternate(*iterators): 
    queue = deque(iterators) 
    while len(queue) > 0: 
     iterator = queue.popleft() 
     try: 
      yield next(iterator) 
      queue.append(iterator) 
     except StopIteration: 
      pass 

Он работает даже тогда, когда некоторые итераторы конечны и другие являются бесконечными:

from itertools import count 

for n in alternate(count(), iter(range(3)), count(100)): 
    input(n) 

печать:

0 
0 
100 
1 
1 
101 
2 
2 
102 
3 
103 
4 
104 
5 
105 
6 
106 

Он также корректно останавливается, если/когда все итераторы были исчерпаны.

Если вы хотите обрабатывать без итераторов итерируемых, как списки, вы можете использовать

def alternate(*iterables): 
    queue = deque(map(iter, iterables)) 
    ...