Itertools Recipe: Sliding Window
The itertools
docs has a ton of slick recipes for using the library to good effect. Some of the code is more useful than illustrative, so I wanted to use these notebooks to break down a few of the functions.
# poor import style, but I want to copy-paste the code
# as-is from the docs
from itertools import *
import itertools
pairwise()
def pairwise(iterable):
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
a, b = tee(iterable)
next(b, None)
return zip(a, b)
This one’s super useful for generating sliding windows over an iterable
Demo
monotonically_increasing = lambda x: x[0] < x[1]
example = list(range(10000))
all(map(monotonically_increasing, pairwise(example)))
True
example = list(range(10000))
example[2001] = 0
all(map(monotonically_increasing, pairwise(example)))
False
Why this works
Simple enough, we get two of the exact same iterable, and then toss the first value of the second one away.
Then the zip()
function will keep calling next()
on each of them together until one of them runs out (the second one will, as it has one less value)
sliding_window()
If we wanted to extend the same functionality but across arbitrarily-many tee’d iterables, we can use the following
def sliding_window(iterable, n=2):
iterables = itertools.tee(iterable, n)
for iterable, num_skipped in zip(iterables, itertools.count()):
for _ in range(num_skipped):
next(iterable, None)
return zip(*iterables)
Demo
example = range(10)
print(list(example))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
for vals in sliding_window(example, 5):
print(list(vals))
[0, 1, 2, 3, 4]
[1, 2, 3, 4, 5]
[2, 3, 4, 5, 6]
[3, 4, 5, 6, 7]
[4, 5, 6, 7, 8]
[5, 6, 7, 8, 9]
Why this works
Same general idea as above, the only difference here is using itertools.count()
to dictate the number of leading values that we toss off of each tee’d iterator. Otherwise, this is a more-generic implementation than the above.