如何合并两个python迭代器?

时间:2008-10-28 16:07:13

标签: python merge iterator

我有两个迭代器,一个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 个答案:

答案 0 :(得分:38)

发电机可以很好地解决您的问题。

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

答案 1 :(得分:15)

你可以做一些几乎与@Pramod首次建议的事情。

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

这种方法的优点是,如果a和b都是无限的,你就不会耗尽内存。

答案 2 :(得分:11)

我也同意不需要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

是的,我只是尝试了不等长的列表,以及包含{}。

的列表

答案 3 :(得分:10)

我会做这样的事情。这将是最节省时间和空间的,因为您不会有将对象压缩在一起的开销。如果ab都是无限的,那么这也会有用。

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

答案 4 :(得分:8)

您可以使用zip以及itertools.chain。如果第一个列表是有限:,这将仅起作用

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

答案 5 :(得分:3)

我不确定您的应用程序是什么,但您可能会发现enumerate()函数更有用。

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

答案 6 :(得分:3)

我更喜欢这种更简洁的方式:

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

答案 7 :(得分:3)

Python不太知名的一个特性是你可以在生成器表达式中拥有更多for子句。对于展平嵌套列表非常有用,例如从zip()/ izip()获得的那些。

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

答案 8 :(得分: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:]

使用实际队列获得更好的性能(如David所建议):

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

答案 9 :(得分: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]

答案 10 :(得分:0)

为什么需要itertools?

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

在这种情况下,a或b中的至少一个必须是有限长度的,因为zip将返回一个列表,而不是迭代器。如果您需要一个迭代器作为输出,那么您可以使用Claudiu解决方案。

答案 11 :(得分:0)

使用itertools.izip(),而不是像其他一些答案中的zip(),将提高性能:

正如“pydoc itertools.izip”所示:“像zip()函数一样工作,但通过返回迭代器而不是列表来消耗更少的内存。”

即使其中一个迭代器是无限的,Itertools.izip也能正常工作。

答案 12 :(得分:0)

一种简洁的方法是使用itertools.cycle()生成器表达式。它避免了创建元组的长链()。

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