在python中将整数列表转换为范围

converting a list of integers into range in python

python中是否存在可以将递增的整数列表转换为范围列表的内容?

例如,给定集合0,1,2,3,4,7,8,9,11我想得到0,4,7,9,11,11。

我可以编写一个程序来实现这一点,但我想知道在Python中是否有内置函数


使用itertools.groupby()可以产生一个简洁但复杂的实现:

1
2
3
4
5
6
7
8
import itertools

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

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

输出:

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


可以将列表理解与生成器表达式以及enumerate()和itertools.groupby()的组合一起使用:

1
2
3
4
5
>>> 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]]

首先,enumerate()将从列表项及其各自的索引中构建元组:

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

然后,groupby()将这些元组分组,使用它们的索引和值之间的差异(连续值将相等):

1
2
>>> [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),)]

从那里,我们只需要从每个组的第一个和最后一个元组的值构建列表(如果组只包含一个项,那么列表是相同的)。

您还可以使用[(t[0][1], t[-1][1]) ...]来构建一个范围元组列表,而不是嵌套列表,甚至可以使用((t[0][1], t[-1][1]) ...)来将整个表达式转换为一个可重复的generator,从而可以在运行时轻松地构建范围元组。


这是对Juanchopanza答案非常优雅的改进。这一个包含非唯一和非排序输入,并且与python3兼容:

1
2
3
4
5
6
7
8
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]

例子:

1
2
3
4
5
>>> 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)]


发电机:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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]))

产生这些结果:

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

请注意,运行的重复数字会被压缩。我不知道这是不是你想要的。如果没有,将>改为!=

我理解你的问题。我研究了itertools,试图想出一个可以在几行python中完成的解决方案,这个解决方案有资格被称为"几乎是内置的",但我什么都想不出来。


生成范围对:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

例子:

1
2
3
>>> 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)]

作为发电机:

1
2
3
4
5
6
7
8
9
10
11
12
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)

例子:

1
2
3
>>> 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'


我认为其他答案很难理解,而且可能效率低下。希望这越来越容易。

1
2
3
4
5
6
7
8
9
10
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


把它改短:

1
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)))


如果在python中没有这样的特性,下面是一个实现

1
2
3
4
5
6
7
8
9
10
11
12
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])

没有内置的,或者在我所知道的任何库中。我知道,没什么帮助,但我从来没有遇到过你想要的东西。

下面是你的程序的一些想法(至少在C++中,但它可以给你一些其他的想法):

将整数集转换为范围