2009-08-01 6 views
5

Digamos que tengo una función de generador de la siguiente manera:¿Puede una función de Python tomar un generador y devolver generadores a subconjuntos de su salida generada?

import random 
def big_gen(): 
    i = 0 
    group = 'a' 
    while group != 'd': 
    i += 1 
    yield (group, i) 
    if random.random() < 0.20: 
     group = chr(ord(group) + 1) 

Ejemplo de salida podría ser: ('a', 1), ('a', 2), ('a', 3), ('a', 4), ('a', 5), ('a', 6), ('a', 7), ('a', 8), ('b', 9), ('c ', 10), (' c ', 11), (' c ', 12), (' c ', 13)

Me gustaría dividir esto en tres grupos: Grupo A, Grupo B y Grupo C. Y me gustaría un generador para cada grupo. Luego pasaría el generador y la carta grupal a una subfunción. Un ejemplo de la subfunción:

def printer(group_letter, generator): 
    print "These numbers are in group %s:" % group_letter 
    for num in generator: 
    print "\t%s" % num 

la salida deseada sería:

These numbers are in group a: 
1 
2 
3 
4 
5 
6 
7 
8 
These numbers are in group b: 
9 
These numbers are in group c: 
10 
11 
12 
13 

Cómo puedo hacer esto sin cambiar big_gen() o una impresora(), y evitar el almacenamiento de todo el grupo en la memoria en ¿una vez? (En la vida real, los grupos son enorme)

+1

En la vida real, ¿está bien caminar el gran generador varias veces? ¿O los datos se consumen de forma irrevocable? Si entiendo correctamente su ejemplo, el estado de la llamada al azar() no se almacena en ningún lugar, por lo que no puede volver a caminar ese generador. – Nelson

Respuesta

8

Claro, esto hace lo que quiere:

import itertools 
import operator 

def main(): 
    for let, gen in itertools.groupby(big_gen(), key=operator.itemgetter(0)): 
    secgen = itertools.imap(operator.itemgetter(1), gen) 
    printer(let, secgen) 

groupby hace la mayor parte del trabajo aquí - el key= simplemente le dice en qué campo agrupar.

El generador resultante tiene que ser envuelto en una imap sólo porque se ha especificado su printer firma a tomar un iterador sobre el número, mientras que, por naturaleza, groupby devuelve iteradores más de los mismos artículos que recibe como su entrada - aquí , Tuplas de 2 ítems con una letra seguida de un número, pero esto no es del todo pertinente al título de tu pregunta.

La respuesta a ese título es que, sí, una función de Python puede perfectamente hacer el trabajo que desea: itertools.groupby, de hecho, hace exactamente eso. Recomiendo estudiar el módulo itertools con cuidado, es una herramienta muy útil (y ofrece un rendimiento espléndido también).

0

Tiene un pequeño problema aquí. Desea que la función de printer() tome un generador para cada grupo, pero en realidad tiene el mismo generador que produce todos los grupos. Tiene dos opciones, tal como lo veo:

1) Cambio big_gen() para producir generadores:

import random 
def big_gen(): 
    i = 0 
    group = 'a' 
    while group != 'd': 
    def gen(): 
     i += 1 
     yield i 
     if random.random() < 0.20: 
      group = chr(ord(group) + 1) 
    yield group, gen 

from itertools import imap 
imap(lambda a: printer(*a), big_gen()) 

2) Cambiar la impresora() para mantener el estado y el aviso cuando los cambios de grupo (manteniendo el original big_gen función()):

def printer(generator): 
    group = None 
    for grp, num in generator: 
    if grp != group: 
     print "These numbers are in group %s:" % grp 
     group = grp 
    print "\t%s" % num 
Cuestiones relacionadas