只是为了展示如何组合itertools recipes,我将使用consume 配方将pairwise 配方尽可能直接地扩展回window 配方:
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 中会稍微快一些(对于足够大的可迭代对象),因为您只需在consume 阶段支付islice 包装开销,而不是在提取每个窗口值的过程中(所以它以n 为界,而不是iterable 中的项目数)。
在性能方面,与其他一些解决方案相比,这非常好(并且比我测试过的任何其他解决方案都更好)。在 Python 3.5.0、Linux x86-64 上测试,使用 ipython %timeit magic。
kindall's the deque solution,通过使用islice而不是自制生成器表达式来调整性能/正确性并测试结果长度,因此当可迭代对象短于窗口时它不会产生结果,以及通过deque 的 maxlen 在位置上而不是按关键字(对于较小的输入会产生惊人的差异):
>>> %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的使用从LEGB中的B移动到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 上的一个变体,它使用默认参数 2 重复使用 tee 来生成嵌套的 tee 对象,因此任何给定的迭代器只能推进一次,而不是独立消耗越来越多的次,类似于MrDrFenner's answer 类似于非内联consume 并且在所有测试中都比内联consume 慢,因此为简洁起见,我省略了这些结果。
如您所见,如果您不关心调用者需要存储结果的可能性,我的 kindall 解决方案的优化版本大部分时间都胜出,除了“大可迭代,小窗口大小case"(其中内联 consume 获胜);它随着可迭代大小的增加而迅速降低,而随着窗口大小的增加根本不降低(所有其他解决方案随着可迭代大小的增加而降低得更慢,但也随着窗口大小的增加而降低)。它甚至可以通过包装 map(tuple, ...) 来适应“需要元组”的情况,它的运行速度比将元组放入函数中要慢一些,但它很简单(需要 1-5% 的时间)并让您保持灵活性当您可以容忍重复返回相同的值时运行得更快。
如果您需要对存储退货的安全性,内联 consume 在除最小输入大小之外的所有输入尺寸上都胜出(非内联 consume 稍慢但类似地缩放)。基于deque & tupling 的解决方案仅在最小输入的情况下获胜,因为设置成本较小,且增益较小;随着可迭代的变长,它会严重退化。
作为记录,我使用的yields tuples 的 kindall 解决方案的改编版本是:
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 的缓存并在每个yield 中使用tuple 以获得更快但不太安全的版本。