2008-10-28 322 views
18

我有兩個迭代器,一個list和一個itertools.count對象(即無限值生成器)。我想這兩個合併成結果的迭代器,交替兩者之間的屈服值:如何合併兩個python迭代器?

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

什麼是最簡單,最簡潔的方式做到這一點?

+0

不要使用這一個人:在項目`列表((產下一(C))或對我我)` – 2017-07-12 12:21:45

回答

36

發電機將很好地解決您的問題。

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

你應該添加一個免責聲明 - 這隻會在列表a是有限的時才起作用。 – Claudiu 2008-10-28 16:17:20

+0

進口itertools DEF的Imerge(A,B): 爲I,J在拉鍊(A,B): 收率我 收率Ĵ C = itertools.count(1) 項= [ '富', '酒吧'] 爲我在imerge(c,項目): 打印我 我想這個,這仍然有效。 len(壓縮列表)= min(l1,l2)。所以有限長度限制不是必需的。 – Pramod 2008-10-28 16:22:58

+2

Claudiu是正確的。嘗試壓縮兩個無限生成器 - 最終會導致內存不足。我寧願使用itertools.izip代替zip。然後,您隨時隨地建立拉鍊,而不是一次完成。你仍然需要注意無限循環,但是嘿。 – 2008-10-28 16:40:28

10

我會做這樣的事情。這將是大多數時間和空間的高效率,因爲您不會將對象壓縮在一起。如果ab都是無限的,這也將起作用。

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

try/except在這裏通過消除StopIteration來破壞迭代器協議,不是嗎? – 2008-10-28 16:46:17

+0

@David Eyk:沒問題,因爲從發電機返回無論如何都會引發StopIteration。這種情況下的try語句是多餘的。 – efotinis 2008-10-28 18:00:24

+0

@efotinis:我不知道這一點。謝謝! – 2008-10-29 03:48:39

7

您可以使用zip以及itertools.chain。這將只有工作如果第一清單有限

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

你可以做一些事情,幾乎是exaclty什麼@Pramod首先提出。

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

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

0

爲什麼需要itertools?

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

在這種情況下,a或b中的至少一個必須是有限長度,因爲zip會返回一個列表,而不是迭代器。如果你需要一個迭代器作爲輸出,那麼你可以去找Claudiu解決方案。

3

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

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

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

,是的,我剛和不等長的名單試了一下,包含列表{}。

0

使用itertools.izip(),而不是拉鍊()在一些其他的答案,將提高性能:

爲「是pydoc itertools.izip」表示:「工程就像拉鍊()函數,但通過返回迭代器而不是列表來消耗更少的內存。「

Itertools。即使其中一個迭代器是無限的,izip也可以正常工作。

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

簡明的方法是使用一臺發電機的表達與itertools.cycle()。它避免了創建元組的長鏈()。

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

我寧願這是更爲簡潔的這另一種方式:

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

一個Python中的鮮爲人知的特點之一是,你可以有更多的條款在發電機表達。對於展開嵌套列表非常有用,例如從zip()/ izip()獲取的列表。

def imerge(*iterators): 
    return (value for row in itertools.izip(*iterators) for value in row) 
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:] 

使用實際隊列獲得更好的性能(由大衛的建議):

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 

它也正確停止如果/當所有迭代器已用盡。

如果要處理非迭代iterables,如列表,你可以使用

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