¿Se puede producir producir múltiples generadores consecutivos?

Aquí hay dos funciones que dividen elementos iterables en sub-listas. Creo que este tipo de tarea está progtwigda muchas veces. Los uso para analizar los archivos de registro que consisten en líneas de repr como (‘resultado’, ‘caso’, 123, 4.56) y (‘volcado’, ..) y así sucesivamente.

Me gustaría cambiarlos para que produzcan iteradores en lugar de listas. Debido a que la lista puede crecer bastante, pero puedo decidir tomarla u omitirla en función de los primeros elementos. Además, si la versión de iter está disponible, me gustaría anidarlas, pero con estas versiones de lista que desperdiciarían algo de memoria duplicando partes.

Pero derivar múltiples generadores de una fuente iterable no es fácil para mí, así que pido ayuda. Si es posible, deseo evitar introducir 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): '''[fft][t][ff] (true) [ff][t][tff](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 

editar: mi propia respuesta

¡Gracias a la respuesta de todos, pude 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 agotar los generadores en todos los niveles, como lo señaló Andrew. Porque de lo contrario, el bucle generador externo 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') 

La respuesta de Adam es buena. Esto es solo en caso de que tengas curiosidad de cómo hacerlo a 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) 

lo que da:

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

(La versión anterior requería un pirateo o, en Python 3, nonlocal porque me asignaron a stream dentro del generator() que hizo (una segunda variable también llamada) stream local a generator() por defecto – 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 el flujo no está disminuyendo.

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

Aquí hay una implementación alternativa que ahora genera 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 una 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) 

Cuyos rendimientos:

  at 0x7f17c7727e60>  [1, 1, 1]  [2, 2]  [3]  [2, 2, 2, 2] 

Implementé lo que describí:

Si lo que desea es rechazar una lista antes de que se devuelva o incluso compile, proporcione un argumento de filtro a las funciones que sería posible. Cuando este filtro rechaza un prefijo de lista, la función eliminará 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]]