title: A better way to split a sequence in chunks with overlaps?
body: <p>I need a function to split an iterable into chunks with the option of having an overlap between the chunks.</p>
  
  <p>I wrote the following code, which gives me the correct output but that is quite inefficient (slow). I can't figure out how to speed it up. Is there a better method?</p>
  
  <pre><code>def split_overlap(seq, size, overlap):
      '''(seq,int,int) =&gt; [[...],[...],...]
      Split a sequence into chunks of a specific size and overlap.
      Works also on strings! 
  
      Examples:
          &gt;&gt;&gt; split_overlap(seq=list(range(10)),size=3,overlap=2)
          [[0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6], [5, 6, 7], [6, 7, 8], [7, 8, 9]]
  
          &gt;&gt;&gt; split_overlap(seq=range(10),size=3,overlap=2)
          [range(0, 3), range(1, 4), range(2, 5), range(3, 6), range(4, 7), range(5, 8), range(6, 9), range(7, 10)]
  
          &gt;&gt;&gt; split_overlap(seq=list(range(10)),size=7,overlap=2)
          [[0, 1, 2, 3, 4, 5, 6], [5, 6, 7, 8, 9]]
      '''
      if size &lt; 1 or overlap &lt; 0:
          raise ValueError('"size" must be an integer with &gt;= 1 while "overlap" must be &gt;= 0')
      result = []
      while True:
          if len(seq) &lt;= size:
              result.append(seq)
              return result
          else:
              result.append(seq[:size])
              seq = seq[size-overlap:]
  </code></pre>
  
  <p><strong>Testing results so far:</strong></p>
  
  <pre><code>l = list(range(10))
  s = 4
  o = 2
  print(split_overlap(l,s,o))
  print(list(split_overlap_jdehesa(l,s,o)))
  print(list(nwise_overlap(l,s,o)))
  print(list(split_overlap_Moinuddin(l,s,o)))
  print(list(gen_split_overlap(l,s,o)))
  print(list(itr_split_overlap(l,s,o)))
  
  [[0, 1, 2, 3], [2, 3, 4, 5], [4, 5, 6, 7], [6, 7, 8, 9]]
  [(0, 1, 2, 3), (2, 3, 4, 5), (4, 5, 6, 7), (6, 7, 8, 9)]
  [(0, 1, 2, 3), (2, 3, 4, 5), (4, 5, 6, 7), (6, 7, 8, 9), (8, 9, None, None)] #wrong
  [[0, 1, 2, 3], [2, 3, 4, 5], [4, 5, 6, 7], [6, 7, 8, 9], [8, 9]] #wrong
  [[0, 1, 2, 3], [2, 3, 4, 5], [4, 5, 6, 7], [6, 7, 8, 9]]
  [(0, 1, 2, 3), (2, 3, 4, 5), (4, 5, 6, 7), (6, 7, 8, 9)]
  
  %%timeit
  split_overlap(l,7,2)
  718 ns ± 2.36 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
  
  %%timeit
  list(split_overlap_jdehesa(l,7,2))
  4.02 µs ± 64.7 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
  
  %%timeit
  list(nwise_overlap(l,7,2))
  5.05 µs ± 102 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
  
  %%timeit
  list(split_overlap_Moinuddin(l,7,2))
  3.89 µs ± 78.7 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
  
  %%timeit
  list(gen_split_overlap(l,7,2))
  1.22 µs ± 13.3 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
  
  %%timeit
  list(itr_split_overlap(l,7,2))
  3.41 µs ± 36.7 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
  </code></pre>
  
  <p><strong>With longer list as input:</strong></p>
  
  <pre><code>l = list(range(100000))
  
  %%timeit
  split_overlap(l,7,2)
  4.27 s ± 132 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
  
  %%timeit
  list(split_overlap_jdehesa(l,7,2))
  31.1 ms ± 495 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
  
  %%timeit
  list(nwise_overlap(l,7,2))
  5.74 ms ± 66 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
  
  %%timeit
  list(split_overlap_Moinuddin(l,7,2))
  16.9 ms ± 89.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
  
  %%timeit
  list(gen_split_overlap(l,7,2))
  4.54 ms ± 22.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
  
  %%timeit
  list(itr_split_overlap(l,7,2))
  19.1 ms ± 240 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
  </code></pre>
  
  <p>From other tests (not reported here), it turned out that for small lists <code>len(list) &lt;= 100</code>, my original implementation <code>split_overlap()</code> is the fastest. But for anything larger than that, <code>gen_split_overlap()</code> is the most efficient solution so far.</p>