2017-05-04 62 views
8

我有一串號碼,說以下內容:轉換號碼的清單範圍

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顯示瞭如何在連續範圍內做到這一點。我不明白我是如何輕鬆做到這一點的。有沒有類似的技巧呢?

回答

2

以下是對問題的簡單介紹。

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 
+0

get_ranges([1,2,4,5,7,9]給出一個範圍[7,9]在結束 – George

+0

@George你會期望。 ?上面的算法會按照預期生成[1,2],[4,5],[7,9],因爲它貪婪地填充範圍。如果你想要一個非貪婪的算法,一個完全不同的方法將是必要的,在這個問題中沒有任何暗示它是這樣的 –

+0

啊拍,我誤解了這個問題,沒關係:) – George

0

它可能不是超短期或優雅,但它似乎工作:

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)] 
1

您可以使用groupbycountitertools模塊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)] 

隨意使用此解決方案並採用或修改它以滿足您的需求。

+0

第二個序列不應該產生一個範圍(10,21,10)嗎? –

+0

是的,當我設置條件'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評論以保持這樣的代碼或修改它。 –

1
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)] 
+0

這僅適用於步長爲1的範圍。 –

+0

@JaredGoguen:作爲練習,添加一個設置步驟的參數。步驟的自動檢測需要初步全面掃描。 – 9000

+0

我不相信這是真的。看到其他答案,包括我的。 –