簡體   English   中英

如何合並兩個 python 迭代器?

[英]How do I merge two python iterators?

我有兩個迭代器,一個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

最簡單、最簡潔的方法是什么?

生成器將很好地解決您的問題。

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

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

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

這種方法的優點是,如果 a 和 b 都是無限的,您將不會耗盡內存。

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

是的,我只是嘗試使用長度不等的列表和包含 {} 的列表。

我會做這樣的事情。 這將最節省時間和空間,因為您不會有將對象壓縮在一起的開銷。 如果ab都是無限a ,這也將起作用。

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

您可以使用zip以及itertools.chain 在第一個列表是有限的情況下才有效

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

我更喜歡另一種更簡潔的方式:

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

Python 鮮為人知的特性之一是您可以在生成器表達式中包含更多的 for 子句。 對於展平嵌套列表非常有用,例如您從 zip()/izip() 獲得的列表。

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

我不確定您的應用程序是什么,但您可能會發現 enumerate() 函數更有用。

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

這是一個優雅的解決方案:

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

一起使用 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]

為什么需要 itertools?

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

在這種情況下,a 或 b 中的至少一個必須是有限長度的,因為 zip 將返回一個列表,而不是一個迭代器。 如果您需要一個迭代器作為輸出,那么您可以使用 Cludiu 解決方案。

使用itertools.izip()代替其他一些答案中的zip()將提高性能:

正如“pydoc itertools.izip”所示:

與 zip() 函數類似,但通過返回迭代器而不是列表來消耗更少的內存。

即使迭代器之一是無限的, Itertools.izip 也能正常工作。

一種簡潔的方法是使用帶有 itertools.cycle() 的生成器表達式。 它避免了創建元組的長鏈()。

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM