只是为了展示如何结合itertools
的食谱,我延长了pairwise
配方尽可能直接回window
用的配方consume
配方:
def consume(iterator, n):
"Advance the iterator n-steps ahead. If n is none, consume entirely."
# Use functions that consume iterators at C speed.
if n is None:
# feed the entire iterator into a zero-length deque
collections.deque(iterator, maxlen=0)
else:
# advance to the empty slice starting at position n
next(islice(iterator, n, n), None)
def window(iterable, n=2):
"s -> (s0, ...,s(n-1)), (s1, ...,sn), (s2, ..., s(n+1)), ..."
iters = tee(iterable, n)
# Could use enumerate(islice(iters, 1, None), 1) to avoid consume(it, 0), but that's
# slower for larger window sizes, while saving only small fixed "noop" cost
for i, it in enumerate(iters):
consume(it, i)
return zip(*iters)
该window
配方是一样的pairwise
,它只是代替在第二的单个元素“消费” tee
-ed迭代器上逐步增加消耗n - 1
迭代器。使用(consume
而不是包装每个迭代器islice
)速度稍快(对于足够大的可迭代对象),因为您只islice
在consume
阶段中支付包装开销,而不是在提取每个窗口值的过程中支付包装开销(因此,它受限制n
,而不是其中的项目数限制)iterable
)。
在性能方面,与其他一些解决方案相比,这是相当不错的(并且比我测试过的其他任何解决方案都要好)。使用ipython
%timeit
魔术在Python 3.5.0,Linux x86-64上进行了测试。
kindall的deque
解决方案,通过使用islice
而不是原始生成器表达式来调整性能/正确性,并测试了结果长度,以便当iterable的长度小于window时,以及maxlen
在deque
位置传递而不是在按关键字(对于较小的输入产生令人惊讶的差异):
>>> %timeit -r5 deque(windowkindall(range(10), 3), 0)
100000 loops, best of 5: 1.87 μs per loop
>>> %timeit -r5 deque(windowkindall(range(1000), 3), 0)
10000 loops, best of 5: 72.6 μs per loop
>>> %timeit -r5 deque(windowkindall(range(1000), 30), 0)
1000 loops, best of 5: 71.6 μs per loop
与以前采用的kindall解决方案相同,但都进行了yield win
更改,yield tuple(win)
因此生成器的存储结果可以正常工作,而所有存储的结果实际上都不是最新结果的视图(在这种情况下,所有其他合理的解决方案都是安全的),并添加tuple=tuple
到函数定义中移动应用tuple
从B
在LEGB
到L
:
>>> %timeit -r5 deque(windowkindalltupled(range(10), 3), 0)
100000 loops, best of 5: 3.05 μs per loop
>>> %timeit -r5 deque(windowkindalltupled(range(1000), 3), 0)
10000 loops, best of 5: 207 μs per loop
>>> %timeit -r5 deque(windowkindalltupled(range(1000), 30), 0)
1000 loops, best of 5: 348 μs per loop
consume
上面显示的基于解决方案:
>>> %timeit -r5 deque(windowconsume(range(10), 3), 0)
100000 loops, best of 5: 3.92 μs per loop
>>> %timeit -r5 deque(windowconsume(range(1000), 3), 0)
10000 loops, best of 5: 42.8 μs per loop
>>> %timeit -r5 deque(windowconsume(range(1000), 30), 0)
1000 loops, best of 5: 232 μs per loop
与相同consume
,但内联的else
情况是consume
避免函数调用和n is None
测试以减少运行时间,尤其是对于小型输入(其中设置开销是工作中有意义的一部分)的情况:
>>> %timeit -r5 deque(windowinlineconsume(range(10), 3), 0)
100000 loops, best of 5: 3.57 μs per loop
>>> %timeit -r5 deque(windowinlineconsume(range(1000), 3), 0)
10000 loops, best of 5: 40.9 μs per loop
>>> %timeit -r5 deque(windowinlineconsume(range(1000), 30), 0)
1000 loops, best of 5: 211 μs per loop
(旁注:其上的变体pairwise
使用tee
默认参数2重复制作嵌套tee
对象,因此任何给定的迭代器仅前进一次,而不是独立消耗越来越多的次数,类似于MrDrFenner的答案类似于非内联的consume
并且比consume
所有测试中的内联速度慢,因此为简洁起见,我省略了这些结果)。
如您所见,如果您不关心调用方是否需要存储结果,我的kindall解决方案的优化版本在大多数情况下都会获胜,除非是在“大迭代,小窗口大小的情况下”(内联consume
获胜) ); 随着可迭代大小的增加,它会迅速降级,而随着窗口大小的增加,它根本不会降级(其他解决方案随着可迭代大小的增加而降级得更慢,但随着窗口大小的增加而降级)。它甚至可以通过包裹入来适应“需要元组”的情况map(tuple, ...)
,它的运行速度比将小结添加到函数中的速度稍慢,但它是微不足道的(需要花费1-5%的时间),并让您保持运行速度更快的灵活性当您可以容忍重复返回相同的值时。
如果您需要安全地防止退货被存储,则内联consume
赢得所有输入(最小输入大小)(非内联consume
则稍慢一些,但缩放比例类似)。基于deque
&捆绑的解决方案仅因最小的安装成本而获得了成功,因为安装成本较小,并且收益很小。随着迭代时间的延长,它的性能将严重下降。
为了记录在案,kindall的解决方案,它的改编版yield
小号tuple
的I所用的是:
def windowkindalltupled(iterable, n=2, tuple=tuple):
it = iter(iterable)
win = deque(islice(it, n), n)
if len(win) < n:
return
append = win.append
yield tuple(win)
for e in it:
append(e)
yield tuple(win)
删除tuple
函数定义行中的缓存并tuple
在每个函数中使用yield
以获得更快但不太安全的版本。
sum()
或max()
)时在每个窗口上执行某种操作 ,则需要牢记的是,有有效的算法可以在恒定时间内(不考虑窗口大小)为每个窗口计算新值。我收集了一些这些算法一起在一个Python库:轧。