2011-01-07 2 views
18

Есть ли что-то существующее в питон, который может преобразовать все больший список целых чисел в список диапазонапреобразования списка целых чисел в диапазон в питоне

Э.Г. учитывая набор {0, 1, 2, 3, 4, 7, 8, 9, 11} Я хочу получить {{0,4}, {7,9}, {11,11}}.

Я могу написать программу, чтобы сделать это, но хочу знать, если есть встроенная функция в Python

+1

Почти такой же вопрос был задан и ответ в http://stackoverflow.com/questions/3429510/pythonic-way-to-convert-a-list-of-integers-into-a-string-of -comma-separate-range/3430231 # 3430231 – Apalala

+1

'>>> import this' – Apalala

+0

Ну, я могу с уверенностью сказать, что я не знаю такой функции. Гораздо сложнее сказать с уверенностью, что чего-то, чего я не знаю, не существует .... –

ответ

25

Использование itertools.groupby производит краткое, но каверзный реализация:

import itertools 

def ranges(i): 
    for a, b in itertools.groupby(enumerate(i), lambda (x, y): y - x): 
     b = list(b) 
     yield b[0][1], b[-1][1] 

print list(ranges([0, 1, 2, 3, 4, 7, 8, 9, 11])) 

Выход:

[(0, 4), (7, 9), (11, 11)] 
+1

Это действительно полезно, мне интересно, можете ли вы объяснить, как работает этот метод, чтобы я мог понять функциональность. это было бы здорово, если это было возможно. – openCivilisation

+0

Для обработки нестандартных и не отсортированных входных данных объемного «i» с «отсортированным (set (i))», см. Https://stackoverflow.com/a/43091576/1201614 – luca

+0

Этот рецепт также доступен в 'more_itertools .consecutive_groups'. См. Демонстрацию [здесь] (https://stackoverflow.com/a/47642650/4531270). – pylang

1

Ничего встроенный, или в каких-либо библиотек, которые я знаю. Не очень полезно, я знаю, но я никогда не сталкивался с чем-то вроде того, что вы хотите.

Вот некоторые идеи для вашей программы по крайней мере (в C++, но он может дать вам другие идеи):

Converting sets of integers into ranges

1

В случае нет такой функции в Python, вот реализация

p = [] 
last = -2                
start = -1 

for item in list: 
    if item != last+1:       
     if start != -1: 
      p.append([start, last]) 
     start = item 
    last = item 

p.append([start, last]) 
2

Этот генератор:

def ranges(p): 
    q = sorted(p) 
    i = 0 
    for j in xrange(1,len(q)): 
     if q[j] > 1+q[j-1]: 
      yield (q[i],q[j-1]) 
      i = j 
    yield (q[i], q[-1]) 

sample = [0, 1, 2, 3, 4, 7, 8, 9, 11] 
print list(ranges(sample)) 
print list(ranges(reversed(sample))) 
print list(ranges([1])) 
print list(ranges([2,3,4])) 
print list(ranges([0,2,3,4])) 
print list(ranges(5*[1])) 

Р roduces этих результатов:

[(0, 4), (7, 9), (11, 11)] 
[(0, 4), (7, 9), (11, 11)] 
[(1, 1)] 
[(2, 4)] 
[(0, 0), (2, 4)] 
[(1, 1)] 

Обратите внимание, что бежит из повторяющихся чисел получить сжатое. Я не знаю, хотите ли вы этого. Если нет, измените значение > на !=.

Я понимаю ваш вопрос. Я посмотрел на itertools и попытался придумать решение, которое можно было бы сделать в нескольких строках Python, которые были бы квалифицированы как «почти встроенный», но я ничего не мог придумать.

7

Вы можете использовать list comprehension с generator expression и сочетание enumerate() и itertools.groupby():

>>> import itertools 
>>> l = [0, 1, 2, 3, 4, 7, 8, 9, 11] 
>>> [[t[0][1], t[-1][1]] for t in 
... (tuple(g[1]) for g in itertools.groupby(enumerate(l), lambda (i, x): i - x))] 
[[0, 4], [7, 9], [11, 11]] 

Во-первых, enumerate() будет строить кортежи из элементов списка и их соответствующий индекс:

>>> [t for t in enumerate(l)] 
[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 7), (6, 8), (7, 9), (8, 11)] 

Тогда groupby() сгруппирует те кортежи, использующие разницу между их индекса и их значение (которое будет одинаковым для последовательных значений):

>>> [tuple(g[1]) for g in itertools.groupby(enumerate(l), lambda (i, x): i - x)] 
[((0, 0), (1, 1), (2, 2), (3, 3), (4, 4)), ((5, 7), (6, 8), (7, 9)), ((8, 11),)] 

Оттуда, нам нужно только создавать списки из значений первых и последних кортежей каждой группы (которая будет таким же, если группа содержит только один элемент).

Вы также можете использовать [(t[0][1], t[-1][1]) ...], чтобы создать список кортежей диапазона вместо вложенных списков, или даже ((t[0][1], t[-1][1]) ...) превратить все выражение в Iterable generator, который лениво строить кортежи диапазона на лету.

1

положить его короче: пары диапазон

ranges=lambda l:map(lambda x:(x[0][1],x[-1][1]),map(lambda (x,y):list(y),itertools.groupby(enumerate(l),lambda (x,y):x-y))) 
+2

Короче нет улучшения, на мой взгляд. – madth3

1

Генерирующие:

def ranges(lst): 
    s = e = None 
    r = [] 
    for i in sorted(lst): 
     if s is None: 
      s = e = i 
     elif i == e or i == e + 1: 
      e = i 
     else: 
      r.append((s, e)) 
      s = e = i 
    if s is not None: 
     r.append((s, e)) 
    return r 

Пример:

>>> lst = [1, 5, 6, 7, 12, 15, 16, 17, 18, 30] 
>>> print repr(ranges(lst)) 
[(1, 1), (5, 7), (12, 12), (15, 18), (30, 30)] 

В качестве генератора:

def gen_ranges(lst): 
    s = e = None 
    for i in sorted(lst): 
     if s is None: 
      s = e = i 
     elif i == e or i == e + 1: 
      e = i 
     else: 
      yield (s, e) 
      s = e = i 
    if s is not None: 
     yield (s, e) 

Пример:

>>> lst = [1, 5, 6, 7, 12, 15, 16, 17, 18, 30] 
>>> print repr(','.join(['%d' % s if s == e else '%d-%d' % (s, e) for (s, e) in gen_ranges(lst)])) 
'1,5-7,12,15-18,30' 
3

Это улучшение по сравнению с очень элегантной @juanchopanza answer. Это одна охватывает неуникальным и нон-отсортирован вход и python3 совместим с:

import itertools 

def to_ranges(iterable): 
    iterable = sorted(set(iterable)) 
    for key, group in itertools.groupby(enumerate(iterable), 
             lambda t: t[1] - t[0]): 
     group = list(group) 
     yield group[0][1], group[-1][1] 

Пример:

>>> x 
[44, 45, 2, 56, 23, 11, 3, 4, 7, 9, 1, 2, 2, 11, 12, 13, 45] 

>>> print(list(to_ranges(x))) 
[(1, 4), (7, 7), (9, 9), (11, 13), (23, 23), (44, 45), (56, 56)] 
0

Я думаю, что другие ответы трудно понять, и вероятно, неэффективен. Надеюсь, это проще и быстрее.

def ranges(ints): 
    ints = sorted(set(ints)) 
    range_start = previous_number = ints[0] 
    for number in ints[1:]: 
     if number == previous_number + 1: 
      previous_number = number 
     else: 
      yield range_start, previous_number 
      range_start = previous_number = number 
    yield range_start, previous_number 
Смежные вопросы