我有一串号码,说以下内容:转换号码的清单范围
1 2 3 4 6 7 8 20 24 28 32
的信息呈现有可能在Python中表示为范围:
[range(1, 5), range(6, 9), range(20, 33, 4)]
在我的输出我会写1..4, 6..8, 20..32..4
,但这只是一个介绍问题。
Another answer显示了如何在连续范围内做到这一点。我不明白我是如何轻松做到这一点的。有没有类似的技巧呢?
我有一串号码,说以下内容:转换号码的清单范围
1 2 3 4 6 7 8 20 24 28 32
的信息呈现有可能在Python中表示为范围:
[range(1, 5), range(6, 9), range(20, 33, 4)]
在我的输出我会写1..4, 6..8, 20..32..4
,但这只是一个介绍问题。
Another answer显示了如何在连续范围内做到这一点。我不明白我是如何轻松做到这一点的。有没有类似的技巧呢?
以下是对问题的简单介绍。
def get_ranges(ls):
N = len(ls)
while ls:
# single element remains, yield the trivial range
if N == 1:
yield range(ls[0], ls[0] + 1)
break
diff = ls[1] - ls[0]
# find the last index that satisfies the determined difference
i = next(i for i in range(1, N) if i + 1 == N or ls[i+1] - ls[i] != diff)
yield range(ls[0], ls[i] + 1, diff)
# update variables
ls = ls[i+1:]
N -= i + 1
它可能不是超短期或优雅,但它似乎工作:
def ranges(ls):
li = iter(ls)
first = next(li)
while True:
try:
element = next(li)
except StopIteration:
yield range(first, first+1)
return
step = element - first
last = element
while True:
try:
element = next(li)
except StopIteration:
yield range(first, last+step, step)
return
if element - last != step:
yield range(first, last+step, step)
first = element
break
last = element
这遍历列表的迭代器,以及收益率范围对象:
>>> list(ranges([1, 2, 3, 4, 6, 7, 8, 20, 24, 28, 32]))
[range(1, 5), range(6, 9), range(20, 33, 4)]
它也处理负范围,并且只有一个元素的范围:
>>> list(ranges([9,8,7, 1,3,5, 99])
[range(9, 6, -1), range(1, 7, 2), range(99, 100)]
您可以使用groupby
和count
从itertools
模块Counter
沿collections
模块这样的例子:
更新:见,以了解该解决方案及其局限性背后的逻辑的评论。
from itertools import groupby, count
from collections import Counter
def ranges_list(data=list, func=range, min_condition=1):
# Sort in place the ranges list
data.sort()
# Find all the steps between the ranges's elements
steps = [v-k for k,v in zip(data, data[1:])]
# Find the repeated items's steps based on condition.
# Default: repeated more than once (min_condition = 1)
repeated = [item for item, count in Counter(steps).items() if count > min_condition]
# Group the items in to a dict based on the repeated steps
groups = {k:[list(v) for _,v in groupby(data, lambda n, c = count(step = k): n-next(c))] for k in repeated}
# Create a dict:
# - keys are the steps
# - values are the grouped elements
sub = {k:[j for j in v if len(j) > 1] for k,v in groups.items()}
# Those two lines are for pretty printing purpose:
# They are meant to have a sorted output.
# You can replace them by:
# return [func(j[0], j[-1]+1,k) for k,v in sub.items() for j in v]
# Otherwise:
final = [(j[0], j[-1]+1,k) for k,v in sub.items() for j in v]
return [func(*k) for k in sorted(final, key = lambda x: x[0])]
ranges1 = [1, 2, 3, 4, 6, 7, 8, 20, 24, 28, 32]
ranges2 = [1, 2, 3, 4, 6, 7, 10, 20, 24, 28, 50,51,59,60]
print(ranges_list(ranges1))
print(ranges_list(ranges2))
输出:
[range(1, 5), range(6, 9), range(20, 33, 4)]
[range(1, 5), range(6, 8), range(20, 29, 4), range(50, 52), range(59, 61)]
限制:
有了这种intput的:
ranges3 = [1,3,6,10]
print(ranges_list(ranges3)
print(ranges_list(ranges3, min_condition=0))
将输出:
# Steps are repeated <= 1 with the condition: min_condition = 1
# Will output an empty list
[]
# With min_condition = 0
# Will output the ranges using: zip(data, data[1:])
[range(1, 4, 2), range(3, 7, 3), range(6, 11, 4)]
随意使用此解决方案并采用或修改它以满足您的需求。
第二个序列不应该产生一个范围(10,21,10)吗? –
是的,当我设置条件'min_confirmation = 0'时,它会输出:[范围(1,5),范围(4,7,2),范围(6,8),范围(7,11,3 ),范围(10,21,10),范围(20,29,4),范围(28,51,22),范围(50,52),范围(51,60,8),范围(59,61) )]'所以'范围(10,21,10)'包括在内。这是列在第三序列的限制下,我认为这将产生不需要的输出。我仍然在等待OP评论以保持这样的代码或修改它。 –
def ranges(data):
result = []
if not data:
return result
idata = iter(data)
first = prev = next(idata)
for following in idata:
if following - prev == 1:
prev = following
else:
result.append((first, prev + 1))
first = prev = following
# There was either exactly 1 element and the loop never ran,
# or the loop just normally ended and we need to account
# for the last remaining range.
result.append((first, prev+1))
return result
测试:
>>> data = range(1, 5) + range(6, 9) + range(20, 24)
>>> print ranges(data)
[(1, 5), (6, 9), (20, 24)]
这仅适用于步长为1的范围。 –
@JaredGoguen:作为练习,添加一个设置步骤的参数。步骤的自动检测需要初步全面扫描。 – 9000
我不相信这是真的。看到其他答案,包括我的。 –
get_ranges([1,2,4,5,7,9]给出一个范围[7,9]在结束 – George
@George你会期望。 ?上面的算法会按照预期生成[1,2],[4,5],[7,9],因为它贪婪地填充范围。如果你想要一个非贪婪的算法,一个完全不同的方法将是必要的,在这个问题中没有任何暗示它是这样的 –
啊拍,我误解了这个问题,没关系:) – George