按连续序列将整数分组的列表

11

我有一个整数列表...

[1,2,3,4,5,8,9,10,11,200,201,202]

我想把它们分成一个列表的列表,每个子列表包含未中断序列的整数。像这样...

[[1,5],[8,11],[200,202]]

我有一个相当笨拙的解决方法...

lSequenceOfNum = [1,2,3,4,5,8,9,10,11,200,201,202]

lGrouped = []
start = 0
for x in range(0,len(lSequenceOfNum)):
    if x != len(lSequenceOfNum)-1:
        if(lSequenceOfNum[x+1] - lSequenceOfNum[x]) > 1:
            lGrouped.append([lSequenceOfNum[start],lSequenceOfNum[x]])
            start = x+1

    else:
        lGrouped.append([lSequenceOfNum[start],lSequenceOfNum[x]])
print lGrouped

这是我能做到的最好了。有没有更符合Python风格的方法呢?谢谢。


考虑跳跃的位置而不是范围,在一个简单的整数数组中存储结果,其中每个条目都是原始数组中对应跳跃的索引。我认为这更简单...如果可能会重复使用或成为库代码,您可以将所有内容封装在类的工作原理中。 - djechlin
我非常确定这是一个重复的问题,尽管我现在无法查找它。 - jamylak
7个回答

13

假设列表总是按升序排列:

from itertools import groupby, count

numberlist = [1,2,3,4,5,8,9,10,11,200,201,202]

def as_range(g):
    l = list(g)
    return l[0], l[-1]

print [as_range(g) for _, g in groupby(numberlist, key=lambda n, c=count(): n-next(c))]

5
我意识到我有点过于复杂化,手动计数比使用稍微复杂的生成器要容易得多:
def ranges(seq):
    start, end = seq[0], seq[0]
    count = start
    for item in seq:
        if not count == item:
            yield start, end
            start, end = item, item
            count = item
        end = item
        count += 1
    yield start, end

print(list(ranges([1,2,3,4,5,8,9,10,11,200,201,202])))

制作:

[(1, 5), (8, 11), (200, 202)]

这个方法非常快:

这个方法(以及旧方法,它们的表现几乎完全相同):

python -m timeit -s "from test import ranges" "ranges([1,2,3,4,5,8,9,10,11,200,201,202])"
1000000 loops, best of 3: 0.47 usec per loop

Jeff Mercado的方法

python -m timeit -s "from test import as_range; from itertools import groupby, count" "[as_range(g) for _, g in groupby([1,2,3,4,5,8,9,10,11,200,201,202], key=lambda n, c=count(): n-next(c))]"
100000 loops, best of 3: 11.1 usec per loop

这个方法比原来的快了20倍以上 - 当然,除非速度很重要,否则这不是一个真正的问题。


我的旧解决方案使用生成器:

import itertools

def resetable_counter(start):
    while True:
        for i in itertools.count(start):
            reset = yield i
            if reset:
                start = reset
                break

def ranges(seq):
    start, end = seq[0], seq[0]
    counter = resetable_counter(start)
    for count, item in zip(counter, seq): #In 2.x: itertools.izip(counter, seq)
        if not count == item:
            yield start, end
            start, end = item, item
            counter.send(item)
        end = item
    yield start, end

print(list(ranges([1,2,3,4,5,8,9,10,11,200,201,202])))

生成:

[(1, 5), (8, 11), (200, 202)]

@Abhijit 我很确定,我已经测试过了。你发现它失败了吗? - Gareth Latty
不太确定,但输出结果并不是预期的。你能看一下这个IDEONE运行链接吗? - Abhijit
@Abhijit 刚刚检查了一下,这似乎是 Python 2.x 和 3.x 的问题。在 3.x 下它可以正常工作... 我会尝试找出原因的。 - Gareth Latty
当然,在2.x中zip()不是惰性的 - 在那里你需要使用itertools.izip() - 现在已修复 - Gareth Latty

2
你可以通过三个步骤高效地完成此操作。
给定:
list1=[1,2,3,4,5,8,9,10,11,200,201,202]

计算不连续点
     [1,2,3,4,5,8,9,10,11 ,200,201,202]
-      [1,2,3,4,5,8,9 ,10 ,11 ,200,201,202]
----------------------------------------
       [1,1,1,1,3,1,1 ,1  ,189,1  ,1]
(index) 1 2 3 4 5 6 7  8   9   10  11 
                *          *
rng = [i+1 for i,e in enumerate((x-y for x,y in zip(list1[1:],list1))) if e!=1] 
>>> rng
[5, 9]

添加边框。
rng = [0] + rng + [len(list1)]
>>> rng
[0, 5, 9,12]

现在计算实际连续范围。

[(list1[i],list1[j-1]) for i,j in zip(list2,list2[1:])]
[(1, 5), (8, 11), (200, 202)]

LB                [0,   5,    9,  12]
UB             [0, 5,   9,    12]
     -----------------------
indexes (LB,UB-1) (0,4) (5,8) (9,11)

1
这个问题很旧了,但我还是想分享我的解决方案。
假设导入了numpy库。
a = [1,2,3,4,5,8,9,10,11,200,201,202]

np.split(a, array(np.add(np.where(diff(a)>1),1)).tolist()[0])

0

伪代码(带有待修复的误差):

jumps = new array;
for idx from 0 to len(array)
if array[idx] != array[idx+1] then jumps.push(idx);

我认为这实际上是一个情况,其中使用索引(就像在C语言中,在Java/Python/Perl等语言改进之前)而不是数组中的对象是有意义的。


0

这里有一个易于阅读的版本:

def close_range(el, it):
    while True:
        el1 = next(it, None)
        if el1 != el + 1:
            return el, el1
        el = el1

def compress_ranges(seq):
    iterator = iter(seq)
    left = next(iterator, None)
    while left is not None:
        right, left1 = close_range(left, iterator)
        yield (left, right)
        left = left1

list(compress_ranges([1, 2, 3, 4, 5, 8, 9, 10, 11, 200, 201, 202]))

0

类似问题:
Python - 使用列表推导式查找递增编号序列
将整数列表转换为逗号分隔范围的字符串的Pythonic方法

input = [1, 2, 3, 4, 8, 10, 11, 12, 17]

i, ii, result = iter(input), iter(input[1:]), [[input[0]]]
for x, y in zip(i,ii):
    if y-x != 1:
        result.append([y])
    else:
        result[-1].append(y)

>>> result
[[1, 2, 3, 4], [8], [10, 11, 12], [17]]

>>> print ", ".join("-".join(map(str,(g[0],g[-1])[:len(g)])) for g in result)
1-4, 8, 10-12, 17

>>> [(g[0],g[-1])[:len(g)] for g in result]
[(1, 4), (8,), (10, 12), (17,)]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接