我最近编写了这个Python 3代码,它应该在给予它的所有迭代中交替。也就是说,如果函数作为参数(first, second, third)
给出,那么它会产生first[0], second[0], third[0], first[1], ...
。如果second
在其他人之前耗尽,那么它将被跳过:second[15], third[16], first[16], third[16], ...
直到所有的迭代都用完为止。
这里是。它是功能性的,但它看起来不是非常“pythonic”。我特别不喜欢必须保留一组标志,告诉我发电机是否为空。
def zipper(*many):
iterators = [iter(x) for x in many]
iHasItems = [True]*len(iterators)
while any(iHasItems):
for n, iterator in enumerate(iterators):
if iHasItems[n]:
try:
yield next(iterator)
except StopIteration:
iHasItems[n] = False
您基本上重新实现了itertools文档roundrobin()
中记录的recipes section函数:
from itertools import cycle, islice
def roundrobin(*iterables):
"roundrobin('ABC', 'D', 'EF') --> A D E B F C"
# Recipe credited to George Sakkis
num_active = len(iterables)
nexts = cycle(iter(it).__next__ for it in iterables)
while num_active:
try:
for next in nexts:
yield next()
except StopIteration:
# Remove the iterator we just exhausted from the cycle.
num_active -= 1
nexts = cycle(islice(nexts, num_active))
每次引发StopIteration
异常时,它会循环遍历迭代器并切掉最后一个;最后一个迭代器总是那个刚用尽的。
具体来说,对于输入示例,nexts
在这些位置开始作为<iter('ABC'), iter('D'), iter('EF')>
的循环列表,num_active
是3
,然后算法继续进行:
A
并离开<iter('D'), iter('EF'), iter('BC')>
D
并离开<iter('EF'), iter('BC'), <iter('')>
E
并离开<iter('BC'), <iter(''), iter('F')>
B
并离开<iter(''), iter('F'), iter('C')>
StopIteration
例外;然后循环在<iter('F'), iter('C'), iter(*stopped*)>
,所以num_active
变成2
,cycle(islice(nexts, 2))
将循环设置为<iter('F'), iter('C')>
和while
循环继续F
并离开<iter('C'), iter('')>
C
并离开<iter(''), iter('')>
之后最后两个空迭代器触发进一步的StopIteration
异常,并且num_active
从2变为1并且while
循环结束。
您可以使用collections.deque()
对象和手动旋转来实现相同的功能:
from collections import deque
def roundrobin(*iterables):
nexts = deque((iter(it).__next__ for it in iterables))
while nexts:
try:
yield nexts[0]()
# rotate the queue to the left for the next step
nexts.rotate(-1)
except StopIteration:
# Remove the iterator we just exhausted from the queue
nexts.popleft()
但这种方法比cycle
变体慢,因为轮换是“手动”完成的,每次迭代都会产生成本,超过了更简单的“耗尽”异常案例实现。
与您的方法一样,这可以节省您不得不反复尝试迭代任何已经耗尽的迭代器,并且与其他人发布的zip_longest()
方法不同,不需要您在每次迭代时测试标记值(item is not None
或not item
或item is not unique_sentinel_singleton
)。
你将iterables
和chain
拉在一起
from itertools import chain, zip_longest
def zipper(*many):
return filter(None, chain(*zip_longest(*many)))