2011-01-07 7 views
18

¿Hay algo que existe en Python que puede convertir una lista cada vez mayor de los números enteros en una lista de rangosconvertir una lista de números enteros en la gama de pitón

P. ej dado el conjunto {0, 1, 2, 3, 4, 7, 8, 9, 11}, quiero obtener {{0,4}, {7,9}, {11,11}}.

puedo escribir un programa para hacer esto, pero quiero saber si hay una función incorporada en Python

+1

Casi la misma pregunta fue formulada y respondida en http://stackoverflow.com/questions/3429510/pythonic-way-to-convert-a-list-of-integers-into-a-string-of -comma-separated-range/3430231 # 3430231 – Apalala

+1

'>>> importar esto' – Apalala

+0

Bueno, puedo decir con confianza que no conozco esa función. Es mucho más difícil decir con confianza que algo de lo que no estoy al tanto no existe ... –

Respuesta

25

Usando itertools.groupby produce un conciso pero difícil aplicación:

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

Salida:

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

Esto es realmente útil, me pregunto si podría explicar cómo funciona este método para que pueda entender la funcionalidad. esto sería genial si es posible. – openCivilisation

+0

Para manejar la entrada no única y no ordenada, rodee 'i' con 'ordenado (conjunto (i))', consulte: https://stackoverflow.com/a/43091576/1201614 – luca

+0

Esta receta también está disponible en 'more_itertools .consecutive_groups'. Vea la demostración [aquí] (https://stackoverflow.com/a/47642650/4531270). – pylang

1

Nada incorporado, o en cualquier biblioteca, que yo sepa. No es muy útil, lo sé, pero nunca he encontrado algo como lo que quieres.

Aquí están algunas ideas para su Atleast programa (en C++, pero se le puede dar algunas otras ideas):

Converting sets of integers into ranges

1

En el caso no existe tal función en Python, aquí es una implementación

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

Este generador:

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

P roduces estos resultados:

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

Nota que corre de números repetidos conseguir comprimido. No sé si eso es lo que quieres. Si no, cambie > a !=.

Entiendo su pregunta. Miré en itertools y traté de pensar en una solución que se podría hacer en un par de líneas de Python, que se habría calificado como "casi un built in", pero no pude encontrar nada.

7

Se puede utilizar un list comprehension con un generator expression y una combinación de enumerate() y 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]] 

En primer lugar, enumerate() construirá tuplas de los elementos de la lista y su respectivo índice:

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

Entonces groupby() agrupará esas tuplas utilizando la diferencia entre su índice y su valor (que será igual para los valores consecutivos):

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

a partir de ahí, sólo tenemos que crear listas de los valores de la primera y última tuplas de cada grupo (que será el mismo si el grupo sólo contiene un artículo).

También puede utilizar [(t[0][1], t[-1][1]) ...] para construir una lista de tuplas rango en lugar de las listas anidadas, o incluso ((t[0][1], t[-1][1]) ...) para convertir toda la expresión en un iterable generator que perezosamente va a construir las tuplas alcance sobre la marcha.

1

ponerlo más cortos: pares de rango

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

Más corto no es una mejora, en mi opinión. – madth3

1

Generación:

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 

Ejemplo:

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

Como generador:

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) 

Ejemplo:

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

Esto es una mejora sobre la muy elegante @juanchopanza answer. Éste cubre no único y no ordenados entrada y es python3 compatibles también:

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] 

Ejemplo:

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

creo que las otras respuestas son difíciles de entender, y probablemente ineficiente. Espero que esto sea más fácil y rápido.

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 
Cuestiones relacionadas