在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)
有没有办法在上述解决方案中加入我的额外要求?如果没有,解决这个问题的最佳方法是什么?
答案 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]]