Как объединить два итератора Python? - PullRequest
21 голосов
/ 28 октября 2008

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

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

Какой самый простой и краткий способ сделать это?

Ответы [ 13 ]

38 голосов
/ 28 октября 2008

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

def imerge(a, b):
    for i, j in itertools.izip(a,b):
        yield i
        yield j
15 голосов
/ 28 октября 2008

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

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

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

11 голосов
/ 06 декабря 2008

Я тоже согласен, что 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

и да, я только что попробовал это со списками неравной длины и списком, содержащим {}.

10 голосов
/ 28 октября 2008

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

def imerge(a, b):
    i1 = iter(a)
    i2 = iter(b)
    while True:
        try:
            yield i1.next()
            yield i2.next()
        except StopIteration:
            return
8 голосов
/ 28 октября 2008

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

merge=itertools.chain(*[iter(i) for i in zip(['foo', 'bar'], itertools.count(1))])
3 голосов
/ 09 ноября 2016

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

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))
    ...
3 голосов
/ 30 марта 2011

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

def imerge(*iterators):
    return (value for row in itertools.izip(*iterators) for value in row)
3 голосов
/ 23 марта 2011

Я предпочитаю другой способ, который гораздо более лаконичен:

iter = reduce(lambda x,y: itertools.chain(x,y), iters)
3 голосов
/ 29 октября 2008

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

>>> items = ['foo', 'bar', 'baz']
>>> for i, item in enumerate(items):
...  print item
...  print i
... 
foo
0
bar
1
baz
2
1 голос
/ 06 декабря 2008

Используйте 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]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...