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
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'
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 an 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)]
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.
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
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)))
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.