2008-10-28 58 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)) 
    ...