2009-08-01 4 views
5

Disons que j'ai une fonction de générateur comme ceci:Une fonction Python peut-elle prendre un générateur et renvoyer des générateurs à des sous-ensembles de sa sortie générée?

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) 

Exemple de sortie pourrait être: ('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)

Je voudrais diviser cela en trois groupes: Groupe A, Groupe B et Groupe C. Et je voudrais un générateur pour chaque groupe. Ensuite, je passerais le générateur et la lettre de groupe dans une sous-fonction. Un exemple de la sous-fonction:

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

La sortie souhaitée serait:

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 

Comment puis-je faire cela sans changer big_gen() ou une imprimante() et éviter de stocker l'ensemble du groupe en mémoire à une fois que? (Dans la vraie vie, les groupes sont énorme)

+1

Dans la vraie vie, est-il possible de marcher plusieurs fois sur le gros générateur? Ou les données sont-elles consommées de manière irrévocable? Si je comprends bien votre exemple, l'état de l'appel à random() n'est stocké nulle part, donc vous ne pouvez plus marcher sur ce générateur. – Nelson

Répondre

8

Bien sûr, cela fait ce que vous voulez:

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 fait la plus grande partie du travail ici - le key= indique simplement à quel champ se grouper.

Le générateur résultant doit être enveloppé dans un imap juste parce que vous avez spécifié votre signature printer prendre un itérateur sur le nombre, alors que, par nature, groupby renvoie itérateurs sur les mêmes éléments qu'il reçoit en entrée - ici , Des tuples à 2 items avec une lettre suivie d'un nombre - mais ce n'est pas vraiment tout ce qui concerne le titre de votre question.

La réponse à ce titre est que, oui, une fonction Python peut parfaitement faire le travail que vous voulez - itertools.groupby fait exactement cela. Je recommande d'étudier attentivement le module itertools, c'est un outil très utile (et offre également des performances exceptionnelles).

0

Vous avez un léger problème ici. Vous aimeriez que la fonction d'imprimante() prenne un générateur pour chaque groupe, mais en réalité vous avez le même générateur qui donne tous les groupes. Vous avez deux options, comme je le vois:

1) Variation big_gen() pour donner des générateurs:

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) Changement imprimante() pour maintenir l'état et l'avis lorsque les changements de groupe (en gardant l'original big_gen() fonction):

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 
Questions connexes