将连续整数分组并容忍1的间隙

时间:2014-01-15 16:02:11

标签: python list grouping itertools

在Python中,给定一个排序整数列表,我会按连续值 将间隔分组为1。

例如,给定一个列表my_list

In [66]: my_list
Out[66]: [0, 1, 2, 3, 5, 6, 10, 11, 15, 16, 18, 19, 20]

我想要以下输出:

[[0, 1, 2, 3, 5, 6], [10, 11], [15, 16, 18, 19, 20]]

现在,如果我不必忍受1的差距,我可以应用解释为here的整洁解决方案:

import itertools
import operator
results = []
for k, g in itertools.groupby(enumerate(my_list), lambda (i,x):i-x):
        group = map(operator.itemgetter(1), g)
        results.append(group)

有没有办法在上述解决方案中加入我的额外要求?如果没有,解决这个问题的最佳方法是什么?

6 个答案:

答案 0 :(得分:11)

如有疑问,您可以随时编写自己的发电机:

def group_runs(li,tolerance=2):
    out = []
    last = li[0]
    for x in li:
        if x-last > tolerance:
            yield out
            out = []
        out.append(x)
        last = x
    yield out

演示:

list(group_runs(my_list))
Out[48]: [[0, 1, 2, 3, 5, 6], [10, 11], [15, 16, 18, 19, 20]]

答案 1 :(得分:8)

Numpy是一个非常有用的工具,并不是很难学习。

O(n)只需一行代码就可以解决这个问题(不包括导入,数据和转换为列表 - 如果你真的需要它):

from numpy import array, diff, where, split
l= [0, 1, 2, 3, 5, 6, 10, 11, 15, 16, 18, 19, 20]
result= split(l, where(diff(l)>2)[0]+1 )
print map(list, result)

更重要的是,如果您需要处理大型列表,那么代码非常,与纯Python解决方案不同

答案 2 :(得分:4)

记住,groupby本身就很蹩脚。 itertools.groupby的优势是关键。对于此特定问题,您需要使用内存创建适当的密钥(无状态密钥在此处不起作用)。

实施

class Key(object):
    def __init__(self, diff):
        self.diff, self.flag, self.prev = diff, [0,1], None
    def __call__(self, elem):
        if self.prev and abs(self.prev - elem) > self.diff:
            self.flag = self.flag[::-1]
        self.prev= elem
        return self.flag[0]

<强>对象

[list(g) for k, g in groupby(my_list, key = Key(2))]
[[0, 1, 2, 3, 5, 6], [10, 11], [15, 16, 18, 19, 20]]

工作原理

每次都需要创建一个新的子列表(curr - prev > threshold),你切换一个标志。有不同的方式来切换标志

  • flag = 1; flag *= -1
  • flag = [0, 1 ]; flag = flag[::-1]
  • flag = 0; flag = 0 if flag else 1

选择你心中所想的

因此,这会生成一个随附的密钥以及您的列表

[0, 1, 2, 3, 5, 6, 10, 11, 15, 16, 18, 19, 20]
[0, 0, 0, 0, 0, 0, 1,  1,  0,  0,  0,  0 , 0]
             <------>  <------>
          Toggle flag  Toggle flag
          0 -> 1, as   1 -> 0, as
          10 - 6 > 2   15 - 11 > 2

现在为itertools.groupby,将具有相同键的连续元素分组,所有带有连续0 s或1 s的键的元素组合在一起

[0, 1, 2, 3, 5, 6, 10, 11, 15, 16, 18, 19, 20]
[0, 0, 0, 0, 0, 0, 1,  1,  0,  0,  0,  0 , 0]

[0, 1, 2, 3, 5, 6], [10, 11], [15, 16, 18, 19, 20]
[0, 0, 0, 0, 0, 0], [1,  1],  [0,  0,  0,  0 , 0]

你的最终结果将是

[0, 1, 2, 3, 5, 6], [10, 11], [15, 16, 18, 19, 20]

答案 3 :(得分:3)

O(nlogn)解决方案(假设输入列表未排序)是首先对您给出的列表进行排序,然后遍历每个值,每当当前值与当前值之间的差异时创建一个新组之前的值至少为3.

<强>演示

>>> my_list = [0, 1, 2, 3, 5, 6, 10, 11, 15, 16, 18, 19, 20]
>>> my_list.sort() # if we can't assume the list is sorted beforehand
>>> groups = [[my_list[0]]] # initialize with the first value in the list
>>> for i, val in enumerate(my_list[1:]):
...     if val - groups[-1][-1] > 2:
...         groups.append( [val] ) # create a new group
...     else:
...         groups[-1].append( val ) # append to the most recent group
... 
>>> groups
[[0, 1, 2, 3, 5, 6], [10, 11], [15, 16, 18, 19, 20]]

答案 4 :(得分:1)

当我想处理连续元素时,我通常会使用zip,您可以使用islice来避免构建列表切片:

from itertools import islice

def group(lst, tol=1):
    """Group vals in sorted iterable lst, allow tol between consecutive vals."""
    output = [[]]
    for current, next_ in zip(lst, islice(lst, 1, None)):
        output[-1].append(current)
        if next_ > current + tol + 1:
            output.append([])
    output[-1].append(lst[-1])
    return output

请注意,在Python 2.x中,您需要使用itertools.izip来避免构建2元组(current, next_)的列表。

答案 5 :(得分:1)

这就是我想出的。有一些冗长的初始化,但它完成了工作。 =)

output = []
prev = my_list[0]
temp_list = [my_list[0]]

for num in my_list[1:]:
    if num-2 > prev:
        output += [temp_list]
        temp_list = [num]
    else:
        temp_list.append(num)
    prev = num
output.append(temp_list)

print output

# [[0, 1, 2, 3, 5, 6], [10, 11], [15, 16, 18, 19, 20]]