我最近写了这个Python 3代码,该代码应该在提供给它的所有可迭代对象之间交替。也就是说,如果函数以参数(first, second, third)
的形式给出,那么它将产生first[0], second[0], third[0], first[1], ...
。如果second
先于其他second[15], third[16], first[16], third[16], ...
用完,则将其跳过: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
,直到所有可迭代项都用尽。
是的。它是功能性的,但看起来并不十分“ pythonic”。我特别不喜欢必须保留一组标记来告诉我生成器是否为空。
{{1}}
答案 0 :(得分:0)
您基本上是在重新实现itertools文档recipes section中记录的roundrobin()
函数:
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到0,并且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()
方法不同,它不需要您测试前哨值({{ 1}}或item is not None
或not item
)。
答案 1 :(得分:-1)
您将iterables
和chain
压缩在一起
from itertools import chain, zip_longest
def zipper(*many):
return filter(None, chain(*zip_longest(*many)))