python - 如何改进这个交替迭代器的 Python 3 代码?
问题描述
我最近编写了这个 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
解决方案
您基本上是在重新实现roundrobin()
itertools 文档配方部分中记录的功能:
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()
其他人发布的方法不同,它不需要您在每次迭代时测试哨兵值(item is not None
或not item
或)。item is not unique_sentinel_singleton
推荐阅读
- git - 在 Git 中跟踪文件权限
- python - 根据列名列表对 Pandas 数据框进行排序
- sqlite - SQLLite 如何在没有列歧义的情况下连接多个表
- rxjs - 如何在 DOM 中尚不存在的元素中使用 Rx.Observable.fromEvent?
- javascript - NodeJS、Express 和 MongoDB 每 24 小时运行一次脚本
- c# - EF 6.13 add-migration 在 VS2017、Microsoft.Build.Framework 上抛出异常
- orientdb - OrientDB - 带有附加字段的 expand()
- rust - 如何从超级 POST 请求中提取数据?
- math - 非线性数据方程
- c - 为什么在函数外部声明时数组大小参数不能是变量?