在尝试reduce()从模块实现方法时functools,我遇到了对迭代器工作原理的误解。使用该方法时iter(),我们接收列表的迭代器seq并将其分配给一个变量seqIter,然后该方法next()将列表的第一个元素传递给该变量value。
然而,当执行循环时,我们使用可迭代对象的for迭代器,而可迭代对象又是列表的迭代器。问题是,在循环的每一圈上它等于什么?xseqIterx
在这段代码中,我们看到了reduce在列表中添加数字的方法的实现:
def reduce(func, seq):
seqIter = iter(seq)
value = next(seqIter)
for x in seqIter:
value = func(value, x)
return value
list = [1,2,3]
print("Sum seq is:", reduce(lambda x,y: x+y, list))
结果:
python .\reduceImplemention.py
Sum seq is: 6
在尝试缩短代码时,出现以下结果:
def reduce(func, seq):
value = next(iter(seq))
for x in iter(seq):
value = func(value, x)
return value
list = [1,2,3]
print("Sum seq is:", reduce(lambda x,y: x+y, list))
根据字符串的解释以括号开头(如数学中一样)的规则,应该先执行表达式iter(seq),然后执行字符串的其余部分。我们还使用 方法将列表迭代器传递iter()到循环中。然而,结果变成:forseq
python .\reduceImplemention.py
Sum seq is: 7
问题可能出在循环中for,因此我们创建一个变量seqIter并为其赋值iter(seq),并在循环中将其更改为for x in seqIter。结果还是一样:
python .\reduceImplemention.py
Sum seq is: 7
因此,问题是:为什么该方法next没有在方法代码块的最开始处应用reduce?还是循环条件?
您使用迭代器两次
iter(seq)而不保存迭代器状态。当您编写 时for x in iter(seq),您实际上是在循环中的每个点为序列创建一个新的迭代器seq,这会导致循环的第一步遗漏您next(seqIter)在代码的第一个版本中已经选取的元素。因此,在循环的第一步中,您从列表的第一个元素开始,然后在循环中跳过它并从第二个元素开始。要解决此问题,您需要在两种情况下使用相同的迭代器。按类型:
这里
seqIter它被创建一次,并且在循环执行期间保留其状态作为纯粹的学术兴趣,我想知道是否有可能在没有迭代器的情况下实现类似的reduce。毫无疑问,迭代器解决方案很棒。
我决定在发电机上实现它。
reduce1 选项很紧凑,但有一个缺点:“*_”存储先前计算的 func 值的元组。请记住 28+ 字节,该解决方案适用于小型列表。尽管在某些情况下这种副作用肯定是有用的。
reduce2 选项消除了第一个选项的缺点,但它需要一个空闲的 for 循环来运行生成器。
reduce3 选项需要连接集合模块并创建一个双端队列类。您可以创建自己的小类,但这会使代码变得混乱。这个选项介于前面的选项之间。 deque 类的“额外”实例是从内存中创建的,但 deque 会自动消耗生成器。双端队列只存储一个值——调用 func 的最后一个结果。
PS如果有人知道获取生成器最后一个元素的更“体面”的选项,我将不胜感激。
旧版本有一些评论中指出的缺点: