2010-11-02 17 views
5

Escribí esta clase para comprimir y expandir listas de números para cadenas de secuencia, incluidos los valores de paso cuando el valor del paso es mayor que 1. El código todavía se siente torpe. ¿Hay bibliotecas que pueden hacer algo como esto? ¿Posiblemente un código más simple?Imprimir cadenas de rango numérico formateadas de una lista de números en python

import re 

class Foo(object): 

    def __init__(self, num_list): 
     self.num_list = sorted(list(set([ int(n) for n in num_list ]))) 
    # end def __init__ 

    def gen_seq_data(self): 
     self.seq_data  = list() 
     index_offset  = None 
     backward_step_value = None 
     forward_step_value = None 
     sub_list   = list() 
     sub_list_step_value = None 
     for index, num in enumerate(self.num_list): 

      if index - 1 < 0: 
       backward_step_value = None 
      # end if 
      else: 
       backward_step_value = num - self.num_list[ index - 1 ] 
      # end else 

      try: 
       forward_step_value = self.num_list[ index + 1 ] - num 
      # end try 
      except IndexError: 
       forward_step_value = None 
      # end except 

      if backward_step_value is None: 
       sub_list.append(num) 
      # end if 
      elif backward_step_value == forward_step_value: 
       sub_list.append(num) 
       if forward_step_value is None: 
        self.seq_data.append((sub_list_step_value, sub_list)) 
       # end if 
      # end if 
      elif backward_step_value == sub_list_step_value: 
       sub_list.append(num) 
       if sub_list: 
        self.seq_data.append((sub_list_step_value, sub_list)) 
       # end if 
       sub_list = list() 
      # end elif 
      else: 
       if sub_list: 
        self.seq_data.append((sub_list_step_value, sub_list)) 
       # end if 
       sub_list = [ num ] 
       if forward_step_value is None: 
        self.seq_data.append((sub_list_step_value, sub_list)) 
       # end if 
      # end else 

      try: 
       sub_list_step_value = sub_list[ -1 ] - sub_list[ -2 ] 
      # end try 
      except IndexError: 
       sub_list_step_value = None 
      # end except 
     # end for 
    # end def gen_seq_object 

    def format_elements(self): 
     format_elements = list() 
     for step, num_list in self.seq_data: 
      if step is None: 
       format_elements.append('%s' % (num_list[ 0 ])) 
      # end if 
      elif step == 1: 
       format_elements.append('%s-%s' % (num_list[ 0 ], num_list[ -1 ])) 
      # end elif 
      else: 
       format_elements.append('%s-%sx%s' % (num_list[ 0 ], num_list[ -1 ], step)) 
      # end else 
     # end for 
     return format_elements 
    # end def format_range 

    def format_range(self): 
     return ','.join(self.format_elements()) 
    # end def format_range 

    def expand_range(self): 
     num_list = list() 
     for r_token in self.format_range().split(','): 
      if r_token.isdigit(): 
       num_list.append(int(r_token)) 
      # end if 
      elif '-' in r_token: 
       if 'x' in r_token: 
        start, end, step = re.split(r'[-|x]', r_token) 
        num_list.extend(range(int(start), int(end) + 1, int(step))) 
       # end if 
       else: 
        start, end = r_token.split('-') 
        num_list.extend(range(int(start), int(end) + 1)) 
       # end else 
      # end elif 
     # end for 
     return num_list 
    # end def expand_range 

# end class Foo 

de entrada/salida:

data = [ 1, 4, 5, 6, 10, 15, 16, 17, 18, 20, 22, 24, 26, 27, 28, 30, 35, 40, 45, 50, 56, 63, 66, 69, 72 ] 

foo = Foo(data) 
foo.gen_seq_data() 

print data 

print foo.format_range() 
1,4-6,10,15-18,20-26x2,27,28,30-50x5,56,63-72x3 

print foo.expand_range() 
[1, 4, 5, 6, 10, 15, 16, 17, 18, 20, 22, 24, 26, 27, 28, 30, 35, 40, 45, 50, 56, 63, 66, 69, 72] 
+0

Corrija el problema indentación. Su código tiene un formato incorrecto. Por favor (1) edite la pregunta, (2) lea las instrucciones de formato en el lado derecho de la página y (3) evite publicar cada pieza de código que tenga. ¿Qué problema tienes? ¿Cuál es la pieza de código más pequeña que muestra este ** problema **? –

+3

@ user494203: Bote las líneas "# end if" etc. son monstruosamente inútiles, especialmente los que ocurren justo antes de 'elif' y 'else'. Si haces eso, las personas pueden estar más inclinadas a leer tu código y sugerir más desambigamientos. –

Respuesta

2

Uno. Elimina todos los comentarios de #END. Son monstruosamente inútiles. Tu sangrado habla por sí mismo. Úselo.

Dos. No hagas de esto una clase. No es un objeto distinto con distintas responsabilidades. Es solo un algoritmo. Compuesto de funciones. En el mejor de los casos es una clase con todos los métodos estáticos.

Tres. Nunca haga esto

for index, num in enumerate(self.num_list): 
     if index - 1 < 0: 
      backward_step_value = None 
     # end if 
     else: 
      backward_step_value = num - self.num_list[ index - 1 ] 
     # end else 

Si el primer elemento es especial, trátelo por separado.

backward_step_value = self.num_list[0] 
for num in self.num_list[1:]: 

Rara vez se necesita el índice para algo como esto. De hecho, la única razón para tener el índice parece ser tratar el primer elemento especialmente.

Finalmente, esto es una "reducción". Utilice una función de generador

def reduce_list(some_list): 
    v= min(some_list) 
    low, high = v, v 
    for v in sorted(some_list)[1:]: 
     if v == high+1: 
      high= high+1 
     else: 
      yield low, high 
    yield low, high 

Eso podría producir su lista de rangos contiguos. Luego puede formatear esos.

format_elements(reduce_list(some_list)) 
+0

Esta solución no logra la parte más complicada de la solución de OP, es decir, identifica no solo rangos continuos, sino que varía con un paso fijo de> 1. – jchl

+0

@jchl: Correcto. El punto no era arreglar su código. El objetivo era proporcionar una forma de revisar el código para que sea menos "torpe". Incluir el paso como una variable en lugar de '1' no debería ser demasiado difícil de hacer como una extensión de esto. –

1

la siguiente solución maneja rangos no contiguos, y también conserva el comportamiento de ignorar rangos de longitud 2.

def reduce_list(seq): 
    l = sorted(set(seq)) 
    low = high = l[0] 
    step = None 
    for v in l[1:]: 
     if step is None or v - high == step: 
      # Extend the current range. 
      step = v - high 
      high = v 
     elif high - low == step: 
      # The current range only has two values. Yield the 
      # first value, and start a new range comprising the 
      # second value and the current value. 
      yield low, low, None 
      step = v - high 
      low = high 
      high = v 
     else: 
      # Yield the current range, and start a new one. 
      yield low, high, step 
      low = high = v 
      step = None 
    if high - low == step: 
     # The final range has only two values. Yield them 
     # individually. 
     yield low, low, None 
     step = None 
     low = high 
    yield low, high, step 

def format_element(low, high, step): 
    if step is None: 
     assert low == high 
     return "%s" % (low,) 
    elif step == 1: 
     return "%s-%s" % (low, high) 
    else: 
     return "%s-%sx%s" % (low, high, step) 

def format_list(seq): 
    return ','.join(format_element(*e) for e in seq) 

Aquí hay algo de código de prueba:

def test(*args): 
    print args, "==", format_list(reduce_list(args)) 

test(1) 
test(1, 2) 
test(1, 2, 3) 
test(0, 10) 
test(0, 10, 20) 
test(0, 10, 11, 12, 14, 16) 
test(0, 2, 4, 8, 16, 32, 64) 
test(0, 1, 3, 4, 6, 7, 9, 10) 
test(0, 1, 3, 6, 10, 15, 21, 28) 

que da salida:

(1,) == 1 
(1, 2) == 1,2 
(1, 2, 3) == 1-3 
(0, 10) == 0,10 
(0, 10, 20) == 0-20x10 
(0, 10, 11, 12, 14, 16) == 0,10-12,14,16 
(0, 2, 4, 8, 16, 32, 64) == 0-4x2,8,16,32,64 
(0, 1, 3, 4, 6, 7, 9, 10) == 0,1,3,4,6,7,9,10 
(0, 1, 3, 6, 10, 15, 21, 28) == 0,1,3,6,10,15,21,28 
+0

Gracias por su ayuda. Estas son excelentes soluciones. – Bip

Cuestiones relacionadas