2017-05-04 13 views
8

mam kilka numerów, powiedzieć, co następuje:Konwersja listę numerów do zakresów

1 2 3 4 6 7 8 20 24 28 32 

przedstawione informacje nie mogą być reprezentowane w Pythonie jak zakresach:

[range(1, 5), range(6, 9), range(20, 33, 4)] 

W moim wyjściu Napisałbym 1..4, 6..8, 20..32..4, ale to tylko kwestia prezentacji.

Another answer pokazuje, jak można to zrobić dla sąsiednich zakresów. Nie widzę, jak mogę to łatwo zrobić dla osiąganych zakresów, jak powyżej. Czy istnieje podobna do tego sztuczka?

Odpowiedz

2

Oto proste podejście do problemu.

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] daje szereg [7, 9] na końcu – George

+0

@George czego można się spodziewać. Powyższy algorytm będzie produkował [1,2], [4,5], [7,9] zgodnie z przeznaczeniem, ponieważ jest chciwie wypełniający zakresy. Jeśli chcesz nie chciwego algorytmu, zupełnie inne podejście byłoby konieczne i tam nie jest niczym w tym pytaniu sugerować, że tak jest. –

+0

Ah strzelać, źle zrozumiałem pytanie, nieważne :) – George

0

To może nie być bardzo krótkie lub elegancki, ale wydaje się działać:

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 

This iteracje nad iterator listy, a plony wahają obiekty:

>>> list(ranges([1, 2, 3, 4, 6, 7, 8, 20, 24, 28, 32])) 
[range(1, 5), range(6, 9), range(20, 33, 4)] 

Obsługuje również zakresy ujemne i zakresy, które mają tylko jeden element:

>>> list(ranges([9,8,7, 1,3,5, 99]) 
[range(9, 6, -1), range(1, 7, 2), range(99, 100)] 
1

Można użyć groupby i count z modułem itertools wraz z Counter od collections modułu jak w poniższym przykładzie:

UPDATE: Zobacz komentarze, aby zrozumieć logikę tego rozwiązania i jego ograniczeń.

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

wyjściowa:

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

Ograniczenia:

Z tego rodzaju intput:

ranges3 = [1,3,6,10] 
print(ranges_list(ranges3) 
print(ranges_list(ranges3, min_condition=0)) 

wyświetli:

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

Możesz korzystać z tego rozwiązania i go adoptować lub modyfikować w celu zaspokojenia swoich potrzeb.

+0

Czy druga sekwencja nie powinna generować 'zakresu (10, 21, 10)'? –

+0

Tak, po ustawieniu warunku 'min_confirmation = 0', wyświetli:' [zasięg (1, 5), zasięg (4, 7, 2), zakres (6, 8), zakres (7, 11, 3), zakres (10, 21, 10), zakres (20, 29, 4), zakres (28, 51, 22), zakres (50, 52), zakres (51, 60, 8), zakres (59, 61))] 'so' zakres (10, 21, 10) 'jest zawarty. Jest to wymienione w trzeciej sekwencji pod ograniczeniem, które zakładam, że spowoduje powstanie niepożądanego wyniku. Wciąż czekam na komentarz do OP, aby zachować kod w ten sposób lub go zmodyfikować. –

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 

Test:

>>> data = range(1, 5) + range(6, 9) + range(20, 24) 
>>> print ranges(data) 
[(1, 5), (6, 9), (20, 24)] 
+0

Działa to tylko dla zakresów z krokiem 1. –

+0

@JaredGoguen: Jako ćwiczenie dodaj parametr, który ustawia krok. Automatyczne wykrywanie kroku wymaga wstępnego pełnego skanowania. – 9000

+0

Nie wierzę, że to prawda. Zobacz inne odpowiedzi, w tym moje. –

Powiązane problemy