将整数列表转换为范围在Python中
在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)]