Recientemente escribí una función para generar ciertas secuencias con restricciones no triviales. El problema vino con una solución recursiva natural. Ahora sucede que, incluso para una entrada relativamente pequeña, las secuencias son varios miles, por lo que preferiría usar mi algoritmo como generador en lugar de usarlo para completar una lista con todas las secuencias.
Aquí hay un ejemplo. Supongamos que queremos calcular todas las permutaciones de una cadena con una función recursiva. El siguiente algoritmo ingenuo toma un argumento adicional 'almacenamiento' y le agrega una permutación cada vez que encuentra uno:
def getPermutations(string, storage, prefix=""):
if len(string) == 1:
storage.append(prefix + string) # <-----
else:
for i in range(len(string)):
getPermutations(string[:i]+string[i+1:], storage, prefix+string[i])
storage = []
getPermutations("abcd", storage)
for permutation in storage: print permutation
(No se preocupe por la ineficiencia, esto es solo un ejemplo).
Ahora quiero convertir mi función en un generador, es decir, producir una permutación en lugar de agregarla a la lista de almacenamiento:
def getPermutations(string, prefix=""):
if len(string) == 1:
yield prefix + string # <-----
else:
for i in range(len(string)):
getPermutations(string[:i]+string[i+1:], prefix+string[i])
for permutation in getPermutations("abcd"):
print permutation
Este código no funciona (la función se comporta como un generador vacío).
¿Me estoy perdiendo de algo? ¿Hay alguna manera de convertir el algoritmo recursivo anterior en un generador sin reemplazarlo por uno iterativo ?
yield from getPermutations(string[:i] + string[i+1:])
, ¡lo cual es más eficiente en muchos sentidos!yield from
requeriría que uses el argumento del acumulador (prefix
).string[i],string[:i]+string[i+1:]
pares. Entonces sería:for letter,rest in first_letter_options(string): for perm in getPermuations(rest): yield letter+perm
Esto evita la
len(string)
recursividad -deep y, en general, es una buena forma de manejar generadores dentro de generadores:flatten
nos permite continuar el progreso en otro generador simplementeyield
ingiriéndolo, en lugar de iterar a través de él yyield
rastrear cada elemento manualmente.Python 3.3 se agregará
yield from
a la sintaxis, lo que permite la delegación natural a un subgenerador:fuente
La llamada interior para getPermutations, también es un generador.
Necesita iterar a través de eso con un bucle for (vea la publicación de @MizardX, ¡que me superó en segundos!)
fuente