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

49 投票
12 回答
35438 浏览
提问于 2025-04-16 09:35

在Python里,有没有什么现成的工具可以把一串递增的整数转换成范围列表?

比如,给定一组数字 {0, 1, 2, 3, 4, 7, 8, 9, 11},我想得到 { {0,4}, {7,9}, {11,11} } 这样的结果。

我可以自己写个程序来实现这个功能,但我想知道Python里有没有内置的函数可以做到这一点。

12 个回答

15

这是对一个非常优雅的回答的改进。这个回答处理了不唯一未排序的输入,并且也兼容python3

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]

示例:

>>> 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)]
16

你可以使用一种叫做 列表推导式 的方法,结合 生成器表达式,以及 enumerate()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]]

首先,enumerate() 会把列表中的每个项目和它们的位置(索引)组合成一个元组:

>>> [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() 会根据这些元组的索引和它们的值之间的差异来把元组分组(对于连续的值,这个差异是相等的):

>>> [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]) ...) 来把整个表达式变成一个可迭代的 生成器,这样它会在需要的时候动态生成范围元组。

61

使用 itertools.groupby() 可以实现一个简洁但有点复杂的功能:

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

输出结果:

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

撰写回答