Pode uma função Python tomar um gerador e geradores de retorno para subconjuntos de sua saída gerada?
Pergunta
Vamos dizer que eu tenho uma função de gerador como este:
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)
Exemplo de saída podem ser: ( 'Um', 1), ( 'a', 2), ( 'um', 3), ( 'um', 4), ( 'um', 5), ( 'um', 6), (' um', 7), ( 'um', 8), ( 'b', 9), ( 'c', 10), ( 'c', 11), ( 'c', 12), ( 'c' , 13)
Gostaria de quebrar este em três grupos: Grupo A, Grupo B e Grupo C. e eu gostaria de um gerador para cada grupo. Então eu passar o gerador e a letra do grupo em uma subfunção. Um exemplo da subfunção:
def printer(group_letter, generator):
print "These numbers are in group %s:" % group_letter
for num in generator:
print "\t%s" % num
A saída desejada seria:
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
Como posso fazer isso sem alterar big_gen () ou impressora (), e evitar o armazenamento de todo o grupo na memória de uma vez? (Na vida real, os grupos são enorme )
Solução
Claro, isso faz o que quiser:
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
faz a maior parte do trabalho aqui - o key=
apenas diz que o campo para agrupar.
O gerador resultando precisa ser envolto em um imap
só porque você tenha especificado sua assinatura printer
tomar um iterador sobre número, enquanto que, por natureza, groupby
retorna iteradores sobre os mesmos itens que ela recebe como sua entrada - aqui, 2-itens tuplas com uma letra seguida de um número -. mas isso não é realmente tudo o que pertinente para o título de sua pergunta ??p>
A resposta a esse título é que, sim, uma função Python pode perfeitamente fazer o trabalho que você quer - itertools.groupby
na verdade faz exatamente isso. Eu recomendo estudar a itertools módulo com cuidado, é uma ferramenta muito útil (e proporciona excelente desempenho como bem).
Outras dicas
Você tem um pequeno problema aqui. Você gostaria que a função da impressora () para tomar um gerador para cada grupo, mas na realidade você tem o mesmo gerador produzindo todos os grupos. Você tem duas opções, como eu vê-lo:
1) Mudança big_gen () para produzir geradores:
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) Mudança da impressora () para manter o estado e notificação quando as alterações de grupo (mantendo o seu big_gen original () função):
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