преобразование списка целых чисел в диапазон в python

20

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

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

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

    
задан Akhil 07.01.2011 в 18:24
источник
  • Ну, я могу с уверенностью сказать, что я не знаю такой функции. Гораздо сложнее сказать с уверенностью, что чего-то, чего я не знаю, не существует ... –  Brett Stottlemyer 07.01.2011 в 18:31
  • Почти тот же вопрос задавали и отвечали в stackoverflow.com/questions/3429510/... –  Apalala 07.01.2011 в 20:59
  • >>> импортировать это –  Apalala 08.01.2011 в 21:55

9 ответов

29

Использование 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)]
    
ответ дан user97370 07.01.2011 в 20:04
  • Это действительно полезно, мне интересно, можете ли вы объяснить, как работает этот метод, чтобы я мог понять функциональность. это было бы здорово, если это было возможно. –  openCivilisation 09.08.2016 в 02:38
  • Чтобы обрабатывать неединственные и не отсортированные входные данные объемного «i» с «отсортированным (set (i))», см.: stackoverflow.com/a/43091576/1201614 –  luca 05.10.2017 в 10:07
  • Этот рецепт также доступен в more_itertools.consecutive_groups. Смотрите демонстрацию здесь. –  pylang 04.12.2017 в 23:03
8

Вы можете использовать понимание списка с помощью выражение генератора и комбинация 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]) ...) , чтобы превратить целое выражение в итерируемый generator , который будет лениво строить кортежи диапазона на лету.     

ответ дан Frédéric Hamidi 07.01.2011 в 20:37
3

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

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

Производит эти результаты:

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

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

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

    
ответ дан Apalala 07.01.2011 в 19:16
3

Это улучшение по сравнению с очень элегантным ответом @juanchopanza . Он охватывает не уникальный и не отсортированный вход и 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)]
    
ответ дан luca 29.03.2017 в 12:57
2

Создание пар диапазонов:

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'
    
ответ дан Curt 29.01.2015 в 21:43
1

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

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

Преобразование наборов целых чисел в диапазоны

    
ответ дан Mark Loeser 07.01.2011 в 18:29
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])
    
ответ дан Akhil 07.01.2011 в 18:56
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)))
    
ответ дан Neuer 30.10.2012 в 00:35
  • Короче нет улучшения, на мой взгляд. –  madth3 30.10.2012 в 01:01
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
    
ответ дан Mike Amy 10.05.2017 в 08:04