2012-05-25 14 views
13

Aquí hay dos funciones que dividen los elementos iterables en sub-listas. Creo que este tipo de tarea está programada muchas veces. Los uso para analizar archivos de registro que constan de repr líneas como ('resultado', 'caso', 123, 4.56) y ('volcado', ..) y así sucesivamente.¿Puede producir múltiples generadores consecutivos?

Me gustaría cambiar esto para que produzcan iteradores en lugar de listas. Porque la lista puede crecer bastante, pero puedo decidir tomarla u omitirla en base a los primeros elementos. Además, si la versión iter está disponible, me gustaría anidarlos, pero con estas versiones de lista que perderían algo de memoria al duplicar partes.

Pero derivar múltiples generadores de una fuente iterativa no me resulta fácil, entonces pido ayuda. Si es posible, deseo evitar la introducción de nuevas clases.

Además, si conoce un título mejor para esta pregunta, por favor dígame.

¡Gracias!

def cleave_by_mark (stream, key_fn, end_with_mark=False): 
    '''[f f t][t][f f] (true) [f f][t][t f f](false)''' 
    buf = [] 
    for item in stream: 
     if key_fn(item): 
      if end_with_mark: buf.append(item) 
      if buf: yield buf 
      buf = [] 
      if end_with_mark: continue 
     buf.append(item) 
    if buf: yield buf 

def cleave_by_change (stream, key_fn): 
    '''[1 1 1][2 2][3][2 2 2 2]''' 
    prev = None 
    buf = [] 
    for item in stream: 
     iden = key_fn(item) 
     if prev is None: prev = iden 
     if prev != iden: 
      yield buf 
      buf = [] 
      prev = iden 
     buf.append(item) 
    if buf: yield buf 

edición: mi propia respuesta

Gracias a la respuesta de todo el mundo, podría escribir lo que pedí! Por supuesto, en cuanto a la función "cleave_for_change" también podría usar itertools.groupby.

def cleave_by_mark (stream, key_fn, end_with_mark=False): 
    hand = [] 
    def gen(): 
     key = key_fn(hand[0]) 
     yield hand.pop(0) 
     while 1: 
      if end_with_mark and key: break 
      hand.append(stream.next()) 
      key = key_fn(hand[0]) 
      if (not end_with_mark) and key: break 
      yield hand.pop(0) 
    while 1: 
     # allow StopIteration in the main loop 
     if not hand: hand.append(stream.next()) 
     yield gen() 

for cl in cleave_by_mark (iter((1,0,0,1,1,0)), lambda x:x): 
    print list(cl), # start with 1 
# -> [1, 0, 0] [1] [1, 0] 
for cl in cleave_by_mark (iter((0,1,0,0,1,1,0)), lambda x:x): 
    print list(cl), 
# -> [0] [1, 0, 0] [1] [1, 0] 
for cl in cleave_by_mark (iter((1,0,0,1,1,0)), lambda x:x, True): 
    print list(cl), # end with 1 
# -> [1] [0, 0, 1] [1] [0] 
for cl in cleave_by_mark (iter((0,1,0,0,1,1,0)), lambda x:x, True): 
    print list(cl), 
# -> [0, 1] [0, 0, 1] [1] [0] 

/

def cleave_by_change (stream, key_fn): 
    '''[1 1 1][2 2][3][2 2 2 2]''' 
    hand = [] 
    def gen(): 
     headkey = key_fn(hand[0]) 
     yield hand.pop(0) 
     while 1: 
      hand.append(stream.next()) 
      key = key_fn(hand[0]) 
      if key != headkey: break 
      yield hand.pop(0) 
    while 1: 
     # allow StopIteration in the main loop 
     if not hand: hand.append(stream.next()) 
     yield gen() 

for cl in cleave_by_change (iter((1,1,1,2,2,2,3,2)), lambda x:x): 
    print list(cl), 
# -> [1, 1, 1] [2, 2, 2] [3] [2] 

PRECAUCIÓN: Si alguien va a usar estos, asegúrese de de escape de los generadores en todos los niveles, como se señaló Andrew. Porque de lo contrario, el bucle externo que genera el generador se reiniciará justo donde el generador interno se fue en lugar de donde comienza el siguiente "bloque".

stream = itertools.product('abc','1234', 'ABCD') 
for a in iters.cleave_by_change(stream, lambda x:x[0]): 
    for b in iters.cleave_by_change(a, lambda x:x[1]): 
     print b.next() 
     for sink in b: pass 
    for sink in a: pass 

('a', '1', 'A') 
('b', '1', 'A') 
('c', '1', 'A') 
+0

Si lo que desea es rechazar una lista antes de que sea devuelto o incluso construir, proporcionando un argumento de filtro para las funciones que serían posibles. Cuando este filtro rechaza un prefijo de lista, la función descarta la lista de salida actual y omite agregar a la lista de salida hasta que se inicie el siguiente grupo. –

Respuesta

8

de Adán es buena. esto es por si acaso usted es curioso cómo hacerlo con la mano:

def cleave_by_change(stream): 
    def generator(): 
     head = stream[0] 
     while stream and stream[0] == head: 
      yield stream.pop(0) 
    while stream: 
     yield generator() 

for g in cleave_by_change([1,1,1,2,2,3,2,2,2,2]): 
    print list(g) 

que da:

[1, 1, 1] 
[2, 2] 
[3] 
[2, 2, 2, 2] 

(versión anterior requiere un truco o, en Python 3, nonlocal porque me asignaron stream dentro de generator() que hizo (una segunda variable también llamada) stream local a generator() de forma predeterminada - crédito a gnibbler en los comentarios).

tenga en cuenta que este enfoque es peligroso: si no "consume" los generadores que se devuelven, obtendrá más y más, porque la transmisión no se está haciendo más pequeña.

+0

No es necesario este truco 'mutable'. Simplemente mutee 'stream' en lugar de reasignarlo. Sugerencia: 'stream.pop (0)' –

+0

oh, maldición, por supuesto. Gracias. –

4

Para su segunda función, puede utilizar itertools.groupby de lograr esto con bastante facilidad.

Aquí está una implementación alternativa que ahora produce generadores en lugar de listas:

from itertools import groupby 

def cleave_by_change2(stream, key_fn): 
    return (group for key, group in groupby(stream, key_fn)) 

Aquí está en acción (con la impresión liberal en el camino, para que pueda ver lo que está pasando):

main_gen = cleave_by_change2([1,1,1,2,2,3,2,2,2,2], lambda x: x) 

print main_gen 

for sub_gen in main_gen: 
    print sub_gen 
    print list(sub_gen) 

que produce: respuesta

<generator object <genexpr> at 0x7f17c7727e60> 
<itertools._grouper object at 0x7f17c77247d0> 
[1, 1, 1] 
<itertools._grouper object at 0x7f17c7724850> 
[2, 2] 
<itertools._grouper object at 0x7f17c77247d0> 
[3] 
<itertools._grouper object at 0x7f17c7724850> 
[2, 2, 2, 2] 
1

he implementado lo que he descrito:

Si lo que desea es rechazar una lista antes de ser devuelta o incluso construcción, proporcionando un argumento de filtro para las funciones que serían posible. Cuando este filtro rechaza un prefijo de lista, la función eliminaría la lista de salida actual y omitirá la adición a la lista de salida hasta que se inicie el siguiente grupo.

def cleave_by_change (stream, key_fn, filter=None): 
    '''[1 1 1][2 2][3][2 2 2 2]''' 
    S = object() 
    skip = False 
    prev = S 
    buf = [] 
    for item in stream: 
     iden = key_fn(item) 
     if prev is S: 
      prev = iden 
     if prev != iden: 
      if not skip: 
       yield buf 
      buf = [] 
      prev = iden 
      skip = False 
     if not skip and filter is not None: 
      skip = not filter(item) 
     if not skip: 
      buf.append(item) 
    if buf: yield buf 

print list(cleave_by_change([1, 1, 1, 2, 2, 3, 2, 2, 2, 2], lambda a: a, lambda i: i != 2)) 
# => [[1, 1, 1], [3]] 
print list(cleave_by_change([1, 1, 1, 2, 2, 3, 2, 2, 2, 2], lambda a: a, lambda i: i == 2)) 
# => [[2, 2], [2, 2, 2, 2]] 
Cuestiones relacionadas