Es gibt mehrere ausgezeichnete Artikel, die über die Optimierungstechnik namens Memoization sprechen. Sie können sie hier und hier aufrufen. Wir werden Ihnen einen kurzen Überblick darüber geben, was Memoisierung ist. Wir gehen noch einen Schritt weiter, indem wir Zeile für Zeile analysieren, was der „memoisierende“ Code tatsächlich tut. Am Ende des Artikels werden Sie die Memoisierung vollständig verstehen.
Memoisierung ist die programmatische Praxis, lange rekursive/iterative Funktionen viel schneller laufen zu lassen.
Wie, fragen Sie? Indem die Werte, die die Funktion nach ihrer ersten Ausführung zurückgibt, zwischengespeichert werden.
Wenn wir denselben Wert in unsere memoisierte Funktion eingeben, gibt sie den im Cache gespeicherten Wert zurück, anstatt die Funktion erneut auszuführen, wodurch die Leistung gesteigert wird. Ihr Programm muss nicht mehr jede Zahl neu berechnen, um ein Ergebnis zu erhalten.
Hört sich toll an, oder? Nun, was noch besser ist, ist, dass es nicht schwer zu verstehen ist, was eine Memoize-Funktion tut, wenn man den Code aufschlüsselt.
Hier ist ein Beispiel für eine grundlegende memo
Funktion:
Lassen Sie uns von vorne beginnen…
Wir geben etwas zurück, was man eine anonyme Schließung nennt. Unsere anonyme Schließung kann jede Variable oder, in diesem Fall, jede Funktion erben, die an memo
übergeben wird. Wir können dann mit dem Argumente-Objekt spielen, das unsere übergebene Funktion bereitstellt.
Anmerkung am Rande: Wenn also jede Funktion ein Argumente-Objekt hat, warum erben wir dann nicht das Argumente-Objekt von memo
? Das liegt daran, dass Schließungen in der Regel nicht das Argumente-Objekt einer äußeren Funktion erben. Wir nutzen diese Regel zu unserem Vorteil, um mit der Funktion zu spielen, die wir memoisieren wollen.
Lassen Sie uns anhand eines sehr einfachen und unpraktischen Beispiels genau aufschlüsseln, was die Memoisierung bewirkt. Übrigens sollten Sie diesen Code oder irgendeinen anderen Code kopieren und einfügen, um ein Gefühl dafür zu bekommen, wie er funktioniert.
Ich denke, wir haben das Wesentliche dieses Codebeispiels verstanden. Es spricht praktisch für sich selbst. Schauen wir uns einen Schnappschuss einer echten Memo-Funktion an:
Nun, da wir wissen, was eine Memo-Funktion tut, erstellen wir einen Funktionsausdruck und geben eine rekursive Fibonacci-Funktion in memo
ein und sehen, was passiert.
//Now we'll test our code!
// =======TEST=======
fib(10)//On the first try we need to load//=> loading…//=> loading…//=> loading…//=> loading…//=> loading…//=> 4 loadings later we get...//=> 89// And on the second try…fib(10)//=> 89//No loading! Yaay!//The cool thing about memoizing the recursive Fibonacci algorithm is that once we make a call for the value of the nth number in the series, we are able to store all the previous numbers in the series.//So if we try...
fib(7) //=> 21
//We don't have to recalculate anything.//And if we want to try fib(11)...
fib(11)
//loading...
//=> 144
//Our memoized recursive function already cached fib(1-10),so all it needed to do was to calculate the cached values. // This is what the cache now looks like:
/*
{ '{"0":0}': 1,
'{"0":1}': 1,
'{"0":2}': 2,
'{"0":3}': 3,
'{"0":4}': 5,
'{"0":5}': 8,
'{"0":6}': 13,
'{"0":7}': 21,
'{"0":8}': 34,
'{"0":9}': 55,
'{"0":10}': 89,
'{"0":11}': 144}
*/
Fazit
Memoisierung wird entmystifiziert, wenn man sie auf Schlüssel-Wert-Paare herunterbricht. Alles, was wir tun, ist, ein Objekt zu erstellen, nach vorhandenen Werten zu suchen, die mit der Benutzereingabe übereinstimmen, und neue Schlüssel-Wert-Paare zu speichern, wenn sie nicht in unserem Objekt vorhanden sind.
Natürlich bedeutet das Speichern all dieser Daten, dass wir Speicherplatz benötigen. Es ist am besten, die Memoisierung auf Funktionen zu implementieren, die rein sind und schwere, sich wiederholende Berechnungen beinhalten.
Bonus:
Nur zum Spaß, hier ist eine funktionale Version unseres Memoizers: