Identify groups of varying continuous numbers in a list
<p>In this <a href="https://stackoverflow.com/questions/2154249/identify-groups-of-continuous-numbers-in-a-list">other SO post</a>, a Python user asked how to group continuous numbers such that any sequences could just be represented by its start/end and any stragglers would be displayed as single items. The accepted answer works brilliantly for continuous sequences.</p>

<p>I need to be able to adapt a similar solution but for a sequence of numbers that have potentially (not always) varying increments. Ideally, how I represent that will also include the increment (so they'll know if it was every 3, 4, 5, nth)</p>

<p>Referencing the original question, the user asked for the following input/output</p>

<pre><code>[2, 3, 4, 5, 12, 13, 14, 15, 16, 17, 20]  # input
[(2,5), (12,17), 20]
</code></pre>

<p>What I would like is the following (Note: I wrote a tuple as the output for clarity but xrange would be preferred using its step variable):</p>

<pre><code>[2, 3, 4, 5, 12, 13, 14, 15, 16, 17, 20]  # input
[(2,5,1), (12,17,1), 20]  # note, the last element in the tuple would be the step value
</code></pre>

<p>And it could also handle the following input</p>

<pre><code>[2, 4, 6, 8, 12, 13, 14, 15, 16, 17, 20]  # input
[(2,8,2), (12,17,1), 20]  # note, the last element in the tuple would be the increment
</code></pre>

<p>I know that <code>xrange()</code> supports a step so it may be possible to even use a variant of the other user's answer. I tried making some edits based on what they wrote in the explanation but I wasn't able to get the result I was looking for.</p>

<p>For anyone that doesn't want to click the original link, the code that was originally posted by <a href="https://stackoverflow.com/users/97828/nadia-alramli">Nadia Alramli</a> is:</p>

<pre><code>ranges = []
for key, group in groupby(enumerate(data), lambda (index, item): index - item):
    group = map(itemgetter(1), group)
    if len(group) &gt; 1:
        ranges.append(xrange(group[0], group[-1]))
    else:
        ranges.append(group[0])
</code></pre>

<p>You can create an iterator to help grouping and try to pull the next element from the following group which will be the end of the previous group:</p>

<pre><code>def ranges(lst):
    it = iter(lst)
    next(it)  # move to second element for comparison
    grps = groupby(lst, key=lambda x: (x - next(it, -float("inf"))))
    for k, v in grps:
        i = next(v)
        try:
            step = next(v) - i  # catches single element v or gives us a step
            nxt = list(next(grps)[1])
            yield xrange(i, nxt.pop(0), step)
            # outliers or another group
            if nxt:
                yield nxt[0] if len(nxt) == 1 else xrange(nxt[0], next(next(grps)[1]), nxt[1] - nxt[0])
        except StopIteration:
            yield i  # no seq
</code></pre>

<p>which give you:</p>

<pre><code>In [2]: l1 = [2, 3, 4, 5, 8, 10, 12, 14, 13, 14, 15, 16, 17, 20, 21]

In [3]: l2 = [2, 4, 6, 8, 12, 13, 14, 15, 16, 17, 20]

In [4]: l3 = [13, 14, 15, 16, 17, 18]

In [5]: s1 = [i + 10 for i in xrange(0, 11, 2)]

In [6]: s2 = [30]

In [7]: s3 = [i + 40 for i in xrange(45)]

In [8]: l4 = s1 + s2 + s3

In [9]: l5 = [1, 2, 5, 6, 9, 10]

In [10]: l6 = {1, 2, 3, 5, 6, 9, 10, 13, 19, 21, 22, 23, 24}

In [11]: 

In [11]: for l in (l1, l2, l3, l4, l5, l6):
   ....:         print(list(ranges(l)))
   ....:     
[xrange(2, 5), xrange(8, 14, 2), xrange(13, 17), 20, 21]
[xrange(2, 8, 2), xrange(12, 17), 20]
[xrange(13, 18)]
[xrange(10, 20, 2), 30, xrange(40, 84)]
[1, 2, 5, 6, 9, 10]
[xrange(1, 3), 5, 6, 9, 10, 13, 19, xrange(21, 24)]
</code></pre>

<p>When the step is <code>1</code> it is not included in the xrange output.</p>

python