Here is a fairly efficient approach, although it does require O(N) auxiliary space, although if the number of runs is small then it shouldn't be significant:
from itertools import groupby
def ngrams(seq):
stop = len(seq)+1
for n in range(2, stop):
for i in range(stop - n):
yield seq[i:i+n]
def get_combos(seq):
runs = []
for _, g in groupby(enumerate(seq), lambda x:x[1]-x[0]):
run = [a for _, a in g]
for x in run:
yield [x]
if len(run) > 1:
runs.append(run)
for run in reversed(runs):
yield from ngrams(run)
Note, this uses this classic approach for grouping consecutive integers. It iterates over the groups of consecutive integers, "runs", and yields any lone integer as a single-element list. If the run is longer than a 1, I add it to a list of runs. Finally, you iterate over the list of runs in reverse, yielding the "n-grams", from order 2 to order len(run).
In action:
>>> A = [0,2,5,6]
>>> B = [5,6,8,9]
>>> C = [6,7,8,9]
>>> list(get_combos(A))
[[0], [2], [5], [6], [5, 6]]
>>> list(get_combos(B))
[[5], [6], [8], [9], [8, 9], [5, 6]]
>>> list(get_combos(C))
[[6], [7], [8], [9], [6, 7], [7, 8], [8, 9], [6, 7, 8], [7, 8, 9], [6, 7, 8, 9]]
Note
The
get_combos
assumes the input is
sorted.
Edit
However, for:
>>> D = [6,7,9,12,13,14,20,21,30]
This will produce:
>>> list(get_combos(D))
[[6], [7], [9], [12], [13], [14], [20], [21], [30], [20, 21], [12, 13], [13, 14], [12, 13, 14], [6, 7]]
I.E., the 3-sequence starts before the 2 sequence of subsequent runs has been yielded. If you want all n-len sequences to be yielded before n+1 len sequences, use the following approach:
from itertools import groupby
def ngrams(seq, max_len):
curr = seq
for n in range(1, max_len + 1):
nxt = []
for run in curr:
run_len = len(run)
if run_len > n:
nxt.append(run)
for i in range(run_len + 1 - n):
yield run[i:i+n]
curr = nxt
def _sub_index(t):
return t[1] - t[0]
def get_consecutive_runs(seq):
grouped = groupby(enumerate(seq), _sub_index)
for _, g in grouped:
yield [a for _, a in g]
def get_combos(seq):
runs = list(get_consecutive_runs(seq))
max_len = max(map(len, runs))
yield from ngrams(runs, max_len)
With the following results:
>>> list(get_combos(D))
[[6], [7], [9], [12], [13], [14], [20], [21], [30], [6, 7], [12, 13], [13, 14], [20, 21], [12, 13, 14]]