converting a list of integers into range in python

Is there something existing in python that can convert an increasing list of integers into a range list

E.g. given the set {0, 1, 2, 3, 4, 7, 8, 9, 11} I want to get { {0,4}, {7,9}, {11,11} }.

I can write a program to do this, but want to know if there is an inbuilt function in python

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

Using itertools.groupby() produces a concise but tricky implementation:

import itertools

def ranges(i):
    for a, b in itertools.groupby(enumerate(i), lambda pair: pair[1] - pair[0]):
        b = list(b)
        yield b[0][1], b[-1][1]

print(list(ranges([0, 1, 2, 3, 4, 7, 8, 9, 11])))

Output:

[(0, 4), (7, 9), (11, 11)]

Method 2

You can use a list comprehension with a generator expression and a combination of enumerate() and itertools.groupby():

>>> import itertools
>>> l = [0, 1, 2, 3, 4, 7, 8, 9, 11]
>>> [[t[0][1], t[-1][1]] for t in
... (tuple(g[1]) for g in itertools.groupby(enumerate(l), lambda (i, x): i - x))]
[[0, 4], [7, 9], [11, 11]]

First, enumerate() will build tuples from the list items and their respective index:

>>> [t for t in enumerate(l)]
[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 7), (6, 8), (7, 9), (8, 11)]

Then groupby() will group those tuples using the difference between their index and their value (which will be equal for consecutive values):

>>> [tuple(g[1]) for g in itertools.groupby(enumerate(l), lambda (i, x): i - x)]
[((0, 0), (1, 1), (2, 2), (3, 3), (4, 4)), ((5, 7), (6, 8), (7, 9)), ((8, 11),)]

From there, we only need to build lists from the values of the first and last tuples of each group (which will be the same if the group only contains one item).

You can also use [(t[0][1], t[-1][1]) ...] to build a list of range tuples instead of nested lists, or even ((t[0][1], t[-1][1]) ...) to turn the whole expression into a iterable generator that will lazily build the range tuples on the fly.

Method 3

This is an improvement over the very elegant answer. This one covers non-unique and non-sorted input and is python3 compatible too:

import itertools

def to_ranges(iterable):
    iterable = sorted(set(iterable))
    for key, group in itertools.groupby(enumerate(iterable),
                                        lambda t: t[1] - t[0]):
        group = list(group)
        yield group[0][1], group[-1][1]

Example:

>>> x
[44, 45, 2, 56, 23, 11, 3, 4, 7, 9, 1, 2, 2, 11, 12, 13, 45]

>>> print( list(to_ranges(x))) 
[(1, 4), (7, 7), (9, 9), (11, 13), (23, 23), (44, 45), (56, 56)]

Method 4

Generating range pairs:

def ranges(lst):
    s = e = None
    r = []
    for i in sorted(lst):
        if s is None:
            s = e = i
        elif i == e or i == e + 1:
            e = i
        else:
            r.append((s, e))
            s = e = i
    if s is not None:
        r.append((s, e))
    return r

Example:

>>> lst = [1, 5, 6, 7, 12, 15, 16, 17, 18, 30]
>>> print repr(ranges(lst))
[(1, 1), (5, 7), (12, 12), (15, 18), (30, 30)]

As a generator:

def gen_ranges(lst):
    s = e = None
    for i in sorted(lst):
        if s is None:
            s = e = i
        elif i == e or i == e + 1:
            e = i
        else:
            yield (s, e)
            s = e = i
    if s is not None:
        yield (s, e)

Example:

>>> lst = [1, 5, 6, 7, 12, 15, 16, 17, 18, 30]
>>> print repr(','.join(['%d' % s if s == e else '%d-%d' % (s, e) for (s, e) in gen_ranges(lst)]))
'1,5-7,12,15-18,30'

Method 5

This generator:

def ranges(p):
    q = sorted(p)
    i = 0
    for j in xrange(1,len(q)):
        if q[j] > 1+q[j-1]:
            yield (q[i],q[j-1])
            i = j
    yield (q[i], q[-1])

sample = [0, 1, 2, 3, 4, 7, 8, 9, 11]
print list(ranges(sample))
print list(ranges(reversed(sample)))
print list(ranges([1]))
print list(ranges([2,3,4]))
print list(ranges([0,2,3,4]))
print list(ranges(5*[1]))

Produces these results:

[(0, 4), (7, 9), (11, 11)]
[(0, 4), (7, 9), (11, 11)]
[(1, 1)]
[(2, 4)]
[(0, 0), (2, 4)]
[(1, 1)]

Note that runs of repeated numbers get compressed. I don’t know if that’s what you want. If not, change the > to a !=.

I understand your question. I looked into itertools and tried to think of a solution that could be done in a couple of lines of Python, which would have qualified as “almost a built in”, but I couldn’t come up with anything.

Method 6

As there hasn’t been a new answer for 2 years or so, here’s one for zombie lovers!

If you don’t want to use itertools or a generator, the following uses logic(!). It uses a set (cf. the question!) for input and returns a list of proper ranges as a result; it’s easy enough to adjust the code to suit though.

def ranges(l_set: set) ->list:
    rb_set = sorted(l_set - {i +1 for i in l_set})
    re_set = sorted(l_set - {i -1 for i in l_set})
    return [range(rb_set[i], re_set[i]+1) for i in range(len(rb_set))]

For example:

>>>ranges({6, 9, 10, 7, 8, 2, 3, 14})
[range(2, 4), range(6, 11), range(14, 15)]

>>>ranges({6, 7, 3, 15, 8, 5, 12, 0, 12, 7, 15, 6, 14, 8, 16})
[range(0, 1), range(3, 4), range(5, 9), range(12, 13), range(14, 17)]

Method 7

Related questions for the case when step sizes other than 1 are of interest and a near duplicate of this question here. A solution for either case that performs well is given here.

Method 8

Nothing built-in, or in any libraries that I know of. Not very helpful, I know, but I’ve never come across anything like what you want.

Here are some ideas for your program atleast (in C++, but it can give you some other ideas):

Converting sets of integers into ranges

Method 9

In the case there is no such feature in python, here is an implementation

p = []
last = -2                                                            
start = -1

for item in list:
    if item != last+1:                        
        if start != -1:
            p.append([start, last])
        start = item
    last = item

p.append([start, last])

Method 10

Put it shorter:

ranges=lambda l:map(lambda x:(x[0][1],x[-1][1]),map(lambda (x,y):list(y),itertools.groupby(enumerate(l),lambda (x,y):x-y)))

Method 11

I think the other answers are hard to understand, and probably inefficient. Hope this is easier and faster.

def ranges(ints):
    ints = sorted(set(ints))
    range_start = previous_number = ints[0]
    for number in ints[1:]:
        if number == previous_number + 1:
            previous_number = number
        else:
            yield range_start, previous_number
            range_start = previous_number = number
    yield range_start, previous_number


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x