28

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

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

4

13 回答 13

45

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

def imerge(a, b):
    for i, j in itertools.izip(a,b):
        yield i
        yield j
于 2008-10-28T16:14:02.387 回答
16

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

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

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

于 2008-10-28T16:59:35.040 回答
13

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

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

于 2008-12-05T22:39:29.963 回答
12

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

def imerge(a, b):
    i1 = iter(a)
    i2 = iter(b)
    while True:
        try:
            yield i1.next()
            yield i2.next()
        except StopIteration:
            return
于 2008-10-28T16:12:19.597 回答
10

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

merge=itertools.chain(*[iter(i) for i in zip(['foo', 'bar'], itertools.count(1))])
于 2008-10-28T16:15:15.707 回答
5

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

iter = reduce(lambda x,y: itertools.chain(x,y), iters)
于 2011-03-23T01:48:00.413 回答
4

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

def imerge(*iterators):
    return (value for row in itertools.izip(*iterators) for value in row)
于 2011-03-30T14:09:21.860 回答
3

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

>>> items = ['foo', 'bar', 'baz']
>>> for i, item in enumerate(items):
...  print item
...  print i
... 
foo
0
bar
1
baz
2
于 2008-10-28T22:03:08.500 回答
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))
    ...
于 2016-11-09T00:10:16.300 回答
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]
于 2008-12-06T00:08:15.180 回答
0

为什么需要 itertools?

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

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

于 2008-10-28T21:34:43.410 回答
0

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

正如“pydoc itertools.izip”所示:

像 zip() 函数一样工作,但通过返回迭代器而不是列表来消耗更少的内存。

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

于 2008-12-05T22:46:37.250 回答
0

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

generator = (it.next() for it in itertools.cycle([i1, i2]))
于 2008-12-26T23:04:50.880 回答