I propose three solutions for this problem:
- a specific one for a window of size 3 with a step of 2,
- a general one with any window and step size,
- skip all this and use existing libraries.
Solution 1: sliding window with hard-coded size=3 and step=2
If you replace the for elem in it:
loop by the equivalent while True:
loop which tries next(it)
until StopIteration
is raised, that will let you use next(it)
twice per iteration instead of just once:
def window_size3_step2(seq):
it = iter(seq)
try:
result = [0,0,next(it)]
except StopIteration:
return
while True:
try:
result = [result[2], next(it), next(it)]
except StopIteration:
break
else:
yield result
My_List= ['adl_01_11', 'adl_01_12', 'adl_01_13', 'adl_01_14', 'adl_02_15', 'adl_02_16', 'adl_02_17', 'adl_02_18', 'adl_02_19', 'adl_02_20', 'adl_02_21', 'adl_02_22']
print(f"{list(window_size3_step2(My_List))}")
Output:
[['adl_01_11', 'adl_01_12', 'adl_01_13'], ['adl_01_13', 'adl_01_14', 'adl_02_15'], ['adl_02_15', 'adl_02_16', 'adl_02_17'], ['adl_02_17', 'adl_02_18', 'adl_02_19'], ['adl_02_19', 'adl_02_20', 'adl_02_21']]
Testing for shorter lists:
for n in range(7):
print(f"len={n} result={list(window_size3_step2(range(n)))}")
len=0 result=[]
len=1 result=[]
len=2 result=[]
len=3 result=[[0, 1, 2]]
len=4 result=[[0, 1, 2]]
len=5 result=[[0, 1, 2], [2, 3, 4]]
len=6 result=[[0, 1, 2], [2, 3, 4]]
Solution 2: general window function with arbitrary size and step
This second solution goes back to using islice
to take into account the given window size argument, which I've renamed size
for clarify, and accepts a step
argument that can also take any positive integer value.
from itertools import islice
def window(seq, size=3, step=1):
if size < 1 or step < 1:
raise ValueError("Nobody likes infinite loops.")
it = iter(seq)
result = list(islice(it, size))
while len(result) == size:
yield result
if step >= size:
result = list(islice(it, step-size, step))
else:
result = result[step:] + list(islice(it, step))
On your input list, window(My_List, size=3, step=2)
, or just window(My_List, step=2)
, returns the list of lists you want.
I've also tested this with a wide variety of seq length, size and step, and I can confirm it works correctly in all cases. E.g., the output of this loop (try it yourself, I don't want to paste this long output here) is correct on every line:
for input_size in range(10):
for window_size in range(1,4):
for step_size in range(1,4):
print(f"len={input_size} size={window_size} step={step_size} "
f"result={list(window(range(input_size), size=window_size, step=step_size))}")
Solution 3: there's a library for this!
The more_itertools library already provides a function doing just this:
I had to install it first:
pip3 install more_itertools
Use it:
from more_itertools import windowed
print(f"{list(windowed(My_List, 3, step=2))}")
[('adl_01_11', 'adl_01_12', 'adl_01_13'), ('adl_01_13', 'adl_01_14', 'adl_02_15'), ('adl_02_15', 'adl_02_16', 'adl_02_17'), ('adl_02_17', 'adl_02_18', 'adl_02_19'), ('adl_02_19', 'adl_02_20', 'adl_02_21'), ('adl_02_21', 'adl_02_22', None)]
It's not exactly what you asked for, though, because it pads the last incomplete window with None
(or any fill value you provide) instead of truncating the end.
While using existing libraries is often a good choice, I learned more creating solutions 1 and 2, and I hope you find value in the progression.
Credits:
I found the more_itertools solution here: https://stackoverflow.com/a/46412374/3216427