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>The <code>itertools</code> <a href="https://docs.python.org/2.6/library/itertools.html#recipes" rel="nofollow">pairwise recipe</a> is one way to solve the problem. Applied with <a href="https://docs.python.org/2.6/library/itertools.html#itertools.groupby" rel="nofollow"><code>itertools.groupby</code></a>, groups of pairs whose mathematical difference are equivalent can be created. The first and last items of each group are then selected for multi-item groups or the last item is selected for singleton groups:</p>

<pre><code>from itertools import groupby, tee, izip


def pairwise(iterable):
    "s -&gt; (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = tee(iterable)
    next(b, None)
    return izip(a, b)

def grouper(lst):
    result = []
    for k, g in groupby(pairwise(lst), key=lambda x: x[1] - x[0]):
        g  = list(g)
        if len(g) &gt; 1:
            try:
                if g[0][0] == result[-1]:
                    del result[-1]
                elif g[0][0] == result[-1][1]:
                    g = g[1:] # patch for duplicate start and/or end
            except (IndexError, TypeError):
                pass
            result.append((g[0][0], g[-1][-1], k))
        else:
            result.append(g[0][-1]) if result else result.append(g[0])
    return result
</code></pre>

<hr>

<p><strong>Trial:</strong> <code>input -&gt; grouper(lst) -&gt; output</code></p>

<pre><code>Input: [2, 3, 4, 5, 12, 13, 14, 15, 16, 17, 20]
Output: [(2, 5, 1), (12, 17, 1), 20]

Input: [2, 4, 6, 8, 12, 13, 14, 15, 16, 17, 20]
Output: [(2, 8, 2), (12, 17, 1), 20]

Input: [2, 4, 6, 8, 12, 12.4, 12.9, 13, 14, 15, 16, 17, 20]
Output: [(2, 8, 2), 12, 12.4, 12.9, (13, 17, 1), 20] # 12 does not appear in the second group
</code></pre>

<hr>

<p><strong>Update</strong>: (<em>patch for duplicate start and/or end values</em>)</p>

<pre><code>s1 = [i + 10 for i in xrange(0, 11, 2)]; s2 = [30]; s3 = [i + 40 for i in xrange(45)]

Input: s1+s2+s3
Output: [(10, 20, 2), (30, 40, 10), (41, 84, 1)]

# to make 30 appear as an entry instead of a group change main if condition to len(g) &gt; 2
Input: s1+s2+s3
Output: [(10, 20, 2), 30, (41, 84, 1)]

Input: [2, 4, 6, 8, 10, 12, 13, 14, 15, 16, 17, 20]
Output: [(2, 12, 2), (13, 17, 1), 20]
</code></pre>

python