In diesem Beitrag werden Fibonacci-Zahlen als Hilfsmittel verwendet, um die Nützlichkeit von Python-Generatoren zu erläutern .
Dieser Beitrag enthält sowohl C ++ - als auch Python-Code.
Fibonacci-Zahlen sind wie folgt definiert: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ....
Oder allgemein:
F0 = 0
F1 = 1
Fn = Fn-1 + Fn-2
Dies kann sehr einfach in eine C ++ - Funktion übertragen werden:
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);
}
Wenn Sie jedoch die ersten sechs Fibonacci-Zahlen drucken möchten, berechnen Sie viele der Werte mit der obigen Funktion neu.
Zum Beispiel: Fib(3) = Fib(2) + Fib(1)
aber Fib(2)
auch neu berechnet Fib(1)
. Je höher der Wert, den Sie berechnen möchten, desto schlechter wird es Ihnen gehen.
Man könnte also versucht sein, das Obige neu zu schreiben, indem man den Zustand im Auge behält 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;
}
Aber das ist sehr hässlich und kompliziert unsere Logik main
. Es wäre besser, sich in unserer main
Funktion keine Sorgen um den Zustand machen zu müssen .
Wir könnten einen vector
von Werten zurückgeben und einen verwenden iterator
, um über diesen Satz von Werten zu iterieren, aber dies erfordert viel Speicher auf einmal für eine große Anzahl von Rückgabewerten.
Zurück zu unserem alten Ansatz: Was passiert, wenn wir neben dem Drucken der Zahlen noch etwas anderes tun möchten? Wir müssten den gesamten Codeblock kopieren und einfügen main
und die Ausgabeanweisungen in das ändern, was wir sonst noch tun wollten. Und wenn Sie Code kopieren und einfügen, sollten Sie erschossen werden. Du willst doch nicht erschossen werden, oder?
Um diese Probleme zu lösen und um nicht erschossen zu werden, können wir diesen Codeblock mithilfe einer Rückruffunktion neu schreiben. Jedes Mal, wenn eine neue Fibonacci-Nummer gefunden wird, rufen wir die Rückruffunktion auf.
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;
}
Dies ist eindeutig eine Verbesserung, Ihre Logik main
ist nicht so überladen, und Sie können mit den Fibonacci-Nummern alles tun, was Sie wollen. Definieren Sie einfach neue Rückrufe.
Das ist aber immer noch nicht perfekt. Was wäre, wenn Sie nur die ersten beiden Fibonacci-Zahlen erhalten und dann etwas tun möchten, dann etwas mehr und dann etwas anderes?
Nun, wir könnten so weitermachen wie bisher und den Status erneut hinzufügen main
, sodass GetFibNumbers von einem beliebigen Punkt aus starten kann. Dies wird unseren Code jedoch weiter aufblähen und sieht für eine einfache Aufgabe wie das Drucken von Fibonacci-Zahlen bereits zu groß aus.
Wir könnten ein Produzenten- und Konsumentenmodell über ein paar Threads implementieren. Dies macht den Code jedoch noch komplizierter.
Sprechen wir stattdessen über Generatoren.
Python hat eine sehr schöne Sprachfunktion, die Probleme wie diese sogenannten Generatoren löst.
Mit einem Generator können Sie eine Funktion ausführen, an einem beliebigen Punkt anhalten und dann dort fortfahren, wo Sie aufgehört haben. Jedes Mal, wenn ein Wert zurückgegeben wird.
Betrachten Sie den folgenden Code, der einen Generator verwendet:
def fib():
pp, p = 0, 1
while 1:
yield pp
pp, p = p, pp+p
g = fib()
for i in range(6):
g.next()
Welches gibt uns die Ergebnisse:
0 1 1 2 3 5
Die yield
Anweisung wird in Verbindung mit Python-Generatoren verwendet. Es speichert den Status der Funktion und gibt den erstellten Wert zurück. Wenn Sie das nächste Mal die Funktion next () am Generator aufrufen, wird sie dort fortgesetzt, wo die Ausbeute aufgehört hat.
Dies ist weitaus sauberer als der Rückruffunktionscode. Wir haben saubereren Code, kleineren Code und viel mehr funktionalen Code (Python erlaubt beliebig große ganze Zahlen).
Quelle
send
Daten an einen Generator zu senden. Sobald Sie das tun, haben Sie eine "Coroutine". Es ist sehr einfach, Muster wie das erwähnte Consumer / Producer mit Coroutinen zu implementieren, da sie keinLock
s benötigen und daher nicht blockieren können. Es ist schwer, Coroutinen zu beschreiben, ohne Fäden zu schlagen, daher sage ich nur, dass Koroutinen eine sehr elegante Alternative zum Einfädeln sind.