如何合并两个 python 迭代器?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/243865/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 19:43:39  来源:igfitidea点击:

How do I merge two python iterators?

pythonmergeiterator

提问by David Eyk

I have two iterators, a listand an itertools.countobject (i.e. an infinite value generator). I would like to merge these two into a resulting iterator that will alternate yield values between the two:

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

What is the simplest, most concise way to do this?

最简单、最简洁的方法是什么?

回答by Pramod

A generator will solve your problem nicely.

生成器将很好地解决您的问题。

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

回答by David Locke

You can do something that is almost exaclty what @Pramod first suggested.

你可以做一些几乎完全符合@Pramod 最初建议的事情。

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

The advantage of this approach is that you won't run out of memory if both a and b are infinite.

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

回答by Tom Swirly

I also agree that itertools is not needed.

我也同意不需要 itertools。

But why stop at 2?

但是为什么要停在 2 点呢?

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

handles any number of iterators from 0 on upwards.

处理从 0 开始的任意数量的迭代器。

UPDATE: DOH! A commenter pointed out that this won't work unless all the iterators are the same length.

更新:DOH!一位评论者指出,除非所有迭代器的长度都相同,否则这将不起作用。

The correct code is:

正确的代码是:

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

and yes, I just tried it with lists of unequal length, and a list containing {}.

是的,我只是尝试使用长度不等的列表和包含 {} 的列表。

回答by Claudiu

I'd do something like this. This will be most time and space efficient, since you won't have the overhead of zipping objects together. This will also work if both aand bare infinite.

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

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

回答by Claudiu

You can use zipas well as itertools.chain. This will only workif the first list is finite:

您可以使用zip以及itertools.chain。这在第一个列表是有限的情况下才有效

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

回答by vampolo

I prefer this other way which is much more concise:

我更喜欢另一种更简洁的方式:

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

回答by Petr Viktorin

One of the less well known features of Python is that you can have more for clauses in a generator expression. Very useful for flattening nested lists, like those you get from zip()/izip().

Python 鲜为人知的特性之一是您可以在生成器表达式中包含更多的 for 子句。对于展平嵌套列表非常有用,例如您从 zip()/izip() 获得的列表。

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

回答by user76284

Here is an elegant solution:

这是一个优雅的解决方案:

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

Using an actual queue for better performance (as suggested by David):

使用实际队列以获得更好的性能(如 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

It works even when some iterators are finite and others are infinite:

即使某些迭代器是有限的而其他迭代器是无限的,它也能工作:

from itertools import count

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

Prints:

印刷:

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

It also correctly stops if/when all iterators have been exhausted.

如果/当所有迭代器都用完时,它也会正确停止。

If you want to handle non-iterator iterables, like lists, you can use

如果你想处理非迭代器可迭代对象,比如列表,你可以使用

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

回答by John Fouhy

I'm not sure what your application is, but you might find the enumerate() function more useful.

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

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

回答by A. Coady

Use izip and chain together:

一起使用 izip 和 chain:

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