En esta publicación se usará Fibonacci numbers como una herramienta para compilar para explicar la utilidad de Python generators.
Esta publicación incluirá códigos C++ y Python.
números
de Fibonacci se definen como la secuencia: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ....
o en general:
F0 = 0
F1 = 1
Fn = Fn-1 + Fn-2
Esto puede ser transferido a una función de C++ muy fácilmente:
size_t Fib(size_t n)
{
//Fib(0) = 0
if(n == 0)
return 0;
//Fib(1) = 1
if(n == 1)
return 1;
//Fib(N) = Fib(N-2) + Fib(N-1)
return Fib(n-2) + Fib(n-1);
}
Pero si desea imprimir los 6 primeros números de Fibonacci, se le recalcular una gran cantidad de los valores con la función anterior.
Por ejemplo: Fib(3) = Fib(2) + Fib(1)
, pero Fib(2)
también recalcula Fib(1)
. Cuanto mayor sea el valor que desea calcular, peor será.
Así que uno puede tener la tentación de volver a escribir lo anterior al realizar un seguimiento del estado en main
.
//Not supported for the first 2 elements of Fib
size_t GetNextFib(size_t &pp, size_t &p)
{
int result = pp + p;
pp = p;
p = result;
return result;
}
int main(int argc, char *argv[])
{
size_t pp = 0;
size_t p = 1;
std::cout << "0 " << "1 ";
for(size_t i = 0; i <= 4; ++i)
{
size_t fibI = GetNextFib(pp, p);
std::cout << fibI << " ";
}
return 0;
}
Pero esto es muy feo, y que complica nuestra lógica en main
, sería mejor no tener que preocuparse por el estado en nuestra función main
.
Podríamos devolver un vector
de valores y usar un iterator
para iterar sobre ese conjunto de valores, pero esto requiere mucha memoria a la vez para una gran cantidad de valores devueltos.
Volviendo a nuestro enfoque anterior, ¿qué sucede si queremos hacer algo más además de imprimir los números? Tendríamos que copiar y pegar todo el bloque de código en main
y cambiar las declaraciones de salida a cualquier otra cosa que quisiéramos hacer. Y si copia y pega el código, entonces debería recibir un disparo. No quieres que te disparen, ¿verdad?
Para resolver estos problemas, y para evitar recibir una vacuna, podemos volver a escribir este bloque de código utilizando una función de devolución de llamada. Cada vez que se encuentra un nuevo número de Fibonacci, llamamos a la función de devolución de llamada.
void GetFibNumbers(size_t max, void(*FoundNewFibCallback)(size_t))
{
if(max-- == 0) return;
FoundNewFibCallback(0);
if(max-- == 0) return;
FoundNewFibCallback(1);
size_t pp = 0;
size_t p = 1;
for(;;)
{
if(max-- == 0) return;
int result = pp + p;
pp = p;
p = result;
FoundNewFibCallback(result);
}
}
void foundNewFib(size_t fibI)
{
std::cout << fibI << " ";
}
int main(int argc, char *argv[])
{
GetFibNumbers(6, foundNewFib);
return 0;
}
Esto es claramente una mejora, en su lógica de main
no es tan desordenado, y se puede hacer lo que quiera con los números de Fibonacci, sólo tiene que definir nuevas devoluciones de llamada.
Pero esto todavía no es perfecto. ¿Qué pasa si solo quiere obtener los primeros 2 números de Fibonacci y luego hacer algo, luego obtener un poco más y luego hacer otra cosa?
Bueno, podríamos seguir como hemos estado, y podríamos comenzar a agregar estado de nuevo en main
, lo que permite a GetFibNumbers comenzar desde un punto arbitrario. Pero esto hinchará aún más nuestro código, y ya parece demasiado grande para una tarea simple como imprimir números de Fibonacci.
Podríamos implementar un modelo de productor y consumidor a través de un par de hilos. Pero esto complica aún más el código.
En lugar de hablar de generadores.
Python tiene una característica de lenguaje muy agradable que resuelve problemas como estos llamados generadores.
Un generador le permite ejecutar una función, detenerse en un punto arbitrario y luego continuar de nuevo donde lo dejó. Cada vez que devuelve un valor.
considere el siguiente código que utiliza un generador:
def fib():
pp, p = 0, 1
while 1:
yield pp
pp, p = p, pp+p
g = fib()
for i in range(6):
g.next()
Lo que nos da los resultados:
La declaración yield
se usa junto con los generadores Python. Guarda el estado de la función y devuelve el valor levantado. La próxima vez que llame a la función next() en el generador, continuará donde dejó el rendimiento.
Esto es mucho más limpio que el código de la función de devolución de llamada. Tenemos un código más limpio, un código más pequeño, y sin mencionar mucho más código funcional (Python permite números enteros arbitrariamente grandes).
Source
Marque esta answer http://stackoverflow.com/a/23530101/736037 – Giri