Questo post userà i numeri di Fibonacci come strumento per sviluppare l'utilità dei generatori Python .
Questo post conterrà sia il codice C ++ che Python.
I numeri di Fibonacci sono definiti come la sequenza: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ....
O in generale:
F0 = 0
F1 = 1
Fn = Fn-1 + Fn-2
Questo può essere trasferito in una funzione C ++ in modo estremamente semplice:
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);
}
Ma se vuoi stampare i primi sei numeri di Fibonacci, ricalcolerai molti valori con la funzione sopra.
Ad esempio :, Fib(3) = Fib(2) + Fib(1)
ma Fib(2)
ricalcola anche Fib(1)
. Più alto è il valore che vuoi calcolare, peggio sarai.
Quindi si può essere tentati di riscrivere quanto sopra tenendo traccia dello stato in main
.
// Not supported for the first two 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;
}
Ma questo è molto brutto e complica la nostra logica main
. Sarebbe meglio non doversi preoccupare dello stato nella nostra main
funzione.
Potremmo restituire a vector
di valori e usare a iterator
per scorrere su quel set di valori, ma ciò richiede molta memoria tutta in una volta per un gran numero di valori di ritorno.
Quindi, tornando al nostro vecchio approccio, cosa succede se volessimo fare qualcos'altro oltre a stampare i numeri? Dovremmo copiare e incollare l'intero blocco di codice main
e modificare le istruzioni di output in qualsiasi altra cosa volessimo fare. E se copi e incolli il codice, dovresti sparare. Non vuoi spararti, vero?
Per risolvere questi problemi ed evitare di essere colpiti, possiamo riscrivere questo blocco di codice usando una funzione di callback. Ogni volta che si incontra un nuovo numero di Fibonacci, chiamiamo la funzione di richiamata.
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;
}
Questo è chiaramente un miglioramento, la tua logica main
non è così ingombra e puoi fare tutto ciò che vuoi con i numeri di Fibonacci, semplicemente definire nuovi callback.
Ma questo non è ancora perfetto. E se volessi ottenere solo i primi due numeri di Fibonacci e poi fare qualcosa, poi prenderne ancora un po ', quindi fare qualcos'altro?
Bene, potremmo continuare come siamo stati, e potremmo ricominciare ad aggiungere stato main
, permettendo a GetFibNumbers di partire da un punto arbitrario. Ma questo gonfia ulteriormente il nostro codice e sembra già troppo grande per un compito semplice come la stampa dei numeri di Fibonacci.
Potremmo implementare un modello produttore e consumatore tramite un paio di thread. Ma questo complica ancora di più il codice.
Parliamo invece di generatori.
Python ha una funzione linguistica molto bella che risolve problemi come questi chiamati generatori.
Un generatore ti consente di eseguire una funzione, fermarti in un punto arbitrario e poi continuare di nuovo da dove eri rimasto. Ogni volta che restituisce un valore.
Considera il seguente codice che utilizza un generatore:
def fib():
pp, p = 0, 1
while 1:
yield pp
pp, p = p, pp+p
g = fib()
for i in range(6):
g.next()
Che ci dà i risultati:
0 1 1 2 3 5
L' yield
istruzione viene utilizzata in congiunzione con i generatori Python. Salva lo stato della funzione e restituisce il valore generato. La prossima volta che chiamate la funzione next () sul generatore, continuerà da dove la resa è stata interrotta.
Questo è di gran lunga più pulito del codice della funzione di callback. Abbiamo un codice più pulito, un codice più piccolo e per non parlare di un codice molto più funzionale (Python consente numeri interi arbitrariamente grandi).
fonte
send
dati ad un generatore. Una volta che lo fai, hai un 'coroutine'. È molto semplice implementare modelli come il menzionato consumatore / produttore con coroutine perché non hanno bisogno di seLock
quindi non possono bloccarsi. È difficile descrivere le coroutine senza battere i fili, quindi dirò solo che le coroutine sono un'alternativa molto elegante al threading.