我有两个迭代器,一个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
最简单,最简洁的方法是什么?
答案 0 :(得分:38)
发电机可以很好地解决您的问题。
def imerge(a, b):
for i, j in itertools.izip(a,b):
yield i
yield j
答案 1 :(得分:15)
你可以做一些几乎与@Pramod首次建议的事情。
def izipmerge(a, b):
for i, j in itertools.izip(a,b):
yield i
yield j
这种方法的优点是,如果a和b都是无限的,你就不会耗尽内存。
答案 2 :(得分:11)
我也同意不需要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
是的,我只是尝试了不等长的列表,以及包含{}。
的列表答案 3 :(得分:10)
我会做这样的事情。这将是最节省时间和空间的,因为您不会有将对象压缩在一起的开销。如果a
和b
都是无限的,那么这也会有用。
def imerge(a, b):
i1 = iter(a)
i2 = iter(b)
while True:
try:
yield i1.next()
yield i2.next()
except StopIteration:
return
答案 4 :(得分:8)
您可以使用zip
以及itertools.chain
。如果第一个列表是有限:,这将仅起作用
merge=itertools.chain(*[iter(i) for i in zip(['foo', 'bar'], itertools.count(1))])
答案 5 :(得分:3)
我不确定您的应用程序是什么,但您可能会发现enumerate()函数更有用。
>>> items = ['foo', 'bar', 'baz']
>>> for i, item in enumerate(items):
... print item
... print i
...
foo
0
bar
1
baz
2
答案 6 :(得分:3)
我更喜欢这种更简洁的方式:
iter = reduce(lambda x,y: itertools.chain(x,y), iters)
答案 7 :(得分:3)
Python不太知名的一个特性是你可以在生成器表达式中拥有更多for子句。对于展平嵌套列表非常有用,例如从zip()/ izip()获得的那些。
def imerge(*iterators):
return (value for row in itertools.izip(*iterators) for value in row)
答案 8 :(得分: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))
...
答案 9 :(得分: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]
答案 10 :(得分:0)
为什么需要itertools?
def imerge(a,b):
for i,j in zip(a,b):
yield i
yield j
在这种情况下,a或b中的至少一个必须是有限长度的,因为zip将返回一个列表,而不是迭代器。如果您需要一个迭代器作为输出,那么您可以使用Claudiu解决方案。
答案 11 :(得分:0)
使用itertools.izip(),而不是像其他一些答案中的zip(),将提高性能:
正如“pydoc itertools.izip”所示:“像zip()函数一样工作,但通过返回迭代器而不是列表来消耗更少的内存。”
即使其中一个迭代器是无限的,Itertools.izip也能正常工作。
答案 12 :(得分:0)
一种简洁的方法是使用itertools.cycle()生成器表达式。它避免了创建元组的长链()。
generator = (it.next() for it in itertools.cycle([i1, i2]))