2009-11-03 17 views
7

Tengo una función sumranges(), que suma todos los rangos de números consecutivos encontrados en una tupla de tuplas. Para ilustrar:Sumar rangos consecutivos Pythonically

def sumranges(nums): 
    return sum([sum([1 for j in range(len(nums[i])) if 
        nums[i][j] == 0 or 
        nums[i][j - 1] + 1 != nums[i][j]]) for 
       i in range(len(nums))]) 

>>> nums = ((1, 2, 3, 4), (1, 5, 6), (19, 20, 24, 29, 400)) 
>>> print sumranges(nums) 
7 

Como se puede ver, devuelve el número de rangos de dígitos consecutivos dentro de la tupla, es decir: len ((1, 2, 3, 4), (1), (5, 6), (19, 20), (24), (29), (400)) = 7. Las tuplas siempre están ordenadas.

Mi problema es que mi sumranges() es terrible. Odio mirarlo. Actualmente solo estoy iterando a través de la tupla y cada subtítulo, asignando un 1 si el número no es (1 + número anterior) y sumando el total. Siento que me falta una manera mucho más fácil de lograr mi objetivo declarado. ¿Alguien sabe una forma más pitonica de hacer esto?

Editar: He analizado todas las respuestas dadas hasta el momento. Gracias a todos por sus respuestas.

El código de evaluación comparativa es como sigue, utilizando un tamaño de muestra de 100 K:

from time import time 
from random import randrange 
nums = [sorted(list(set(randrange(1, 10) for i in range(10)))) for 
     j in range(100000)] 

for func in sumranges, alex, matt, redglyph, ephemient, ferdinand: 
    start = time() 
    result = func(nums) 
    end = time() 
    print ', '.join([func.__name__, str(result), str(end - start) + ' s']) 

Los resultados son como sigue. respuesta real se muestra para verificar que todas las funciones devuelven la respuesta correcta:

sumranges, 250281, 0.54171204567 s 
alex, 250281, 0.531121015549 s 
matt, 250281, 0.843333005905 s 
redglyph, 250281, 0.366822004318 s 
ephemient, 250281, 0.805964946747 s 
ferdinand, 250281, 0.405596971512 s 

RedGlyph no borde en términos de velocidad, pero la respuesta más sencilla es probablemente de Fernando, y probablemente para la mayoría gana Pythonic.

Respuesta

14

Mis 2 centavos:

>>> sum(len(set(x - i for i, x in enumerate(t))) for t in nums) 
7 

Es básicamente la misma idea que en descriped Alex' post, pero utilizando un set en lugar de itertools.groupby, lo que resulta en una expresión más corta. Dado que set s se implementan en C y len() de un conjunto se ejecuta en tiempo constante, esto también debe ser bastante rápido.

+2

Eso es realmente astuto. –

+0

Ah, me había perdido las "tuplas siempre se ordenan" poco en la pregunta, ese bit _desea_ hace que esta respuesta sea preferible (la mía funcionaría para las tuplas arbitrarias, no solo las ordenadas, pero hay un pequeño precio que pagar por eso generalidad). –

9

Considere:

>>> nums = ((1, 2, 3, 4), (1, 5, 6), (19, 20, 24, 29, 400)) 
>>> flat = [[(x - i) for i, x in enumerate(tu)] for tu in nums] 
>>> print flat 
[[1, 1, 1, 1], [1, 4, 4], [19, 19, 22, 26, 396]] 
>>> import itertools 
>>> print sum(1 for tu in flat for _ in itertools.groupby(tu)) 
7 
>>> 

que "aplanar" la "creciente" rampas de interés restando el índice del valor, convirtiéndolos en "corre" consecutivos de valores idénticos; entonces identificamos y pudimos "correr" con el precioso itertools.groupby. Esto parece ser una solución bastante elegante (y rápida) para su problema.

+3

Acaba de perder la cabeza. –

+2

Me pregunto si 'sum (len (list (itertools.groupby (tu))) para tu in flat)' sería más rápido o más lento. que 'suma (1 por ...)'. – ephemient

+2

@ephemient, why wonder? ¡Medida! 'python -mtimeit' es tu amigo. –

0

Esto probablemente podría poner juntos en una forma más compacta, pero creo que la claridad sufriría:

def pairs(seq): 
    for i in range(1,len(seq)): 
     yield (seq[i-1], seq[i]) 

def isadjacent(pair): 
    return pair[0]+1 == pair[1] 

def sumrange(seq): 
    return 1 + sum([1 for pair in pairs(seq) if not isadjacent(pair)]) 

def sumranges(nums): 
    return sum([sumrange(seq) for seq in nums]) 


nums = ((1, 2, 3, 4), (1, 5, 6), (19, 20, 24, 29, 400)) 
print sumranges(nums) # prints 7 
+2

Sería bueno si comparas las diversas soluciones propuestas y nos digas cómo funcionan. Así es como las personas decidieron usar '' .join (secuencia) como un modismo de Python, mediante la evaluación comparativa de todas las diferentes formas en que podría ser codificada. –

+0

Resumiré los puntos de referencia en mi pregunta. Además, gracias por la respuesta Matt. –

0

que probablemente podría hacer esto mejor si tuviera un IntervalSet class porque entonces sería escanear a través de sus rangos a construye tu IntervalSet, luego solo usa el conteo de miembros establecidos.

Algunas tareas no siempre se prestan a un buen código, especialmente si necesita escribir el código para el rendimiento.

7

sólo para mostrar algo más cercano a su código original:

def sumranges(nums): 
    return sum((1 for i in nums 
        for j, v in enumerate(i) 
        if j == 0 or v != i[j-1] + 1)) 

La idea era:

  • evitar la construcción de listas intermedias pero el uso de un generador en su lugar, se ahorrará algunos recursos
  • evite usar índices cuando ya ha seleccionado un subelemento (i y v arriba).

El sum() restante todavía es necesario con mi ejemplo.

0

Hay una fórmula para esto, la suma de los primeros n números, 1+ 2+ ... + n = n (n + 1)/2. Entonces, si quieres tener la suma de i-j entonces es (j (j + 1)/2) - (i (i + 1)/2) estoy seguro de que se simplifica pero puedes resolverlo. Puede que no sea pitónico, pero es lo que usaría.

+0

¿Puedes explicar esto un poco? Tenga en cuenta que no estoy buscando la suma de los números, sino un recuento de las secuencias consecutivas en mis tuplas. –

+0

Es ver, estoy respondiendo la pregunta incorrecta, extraño entender. ¿Por qué no encontrar la différance de todo, entonces guarde solamente 1s y agregue entonces o Len() la lista. Mostraría pero escribir en mi teléfono apesta – Vincent

1

Aquí está mi intento:

def ranges(ls): 
    for l in ls: 
     consec = False 
     for (a,b) in zip(l, l[1:]+(None,)): 
      if b == a+1: 
       consec = True 
      if b is not None and b != a+1: 
       consec = False 
      if consec: 
       yield 1 

''' 
>>> nums = ((1, 2, 3, 4), (1, 5, 6), (19, 20, 24, 29, 400)) 
>>> print sum(ranges(nums)) 
7 
''' 

Se ve en el número de pares, comprobando si son un par consecutivo (a menos que sea en el último elemento de la lista). Cada vez que hay un par de números consecutivos rinde 1.

+0

Tuve que alterar esto un poco para trabajar con mi punto de referencia (más notablemente haciendo una lista de la expresión del generador). Su tiempo en 0.64s. Creo que usar una expresión de generador es una solución interesante, sin embargo. –