[1, 2, 3, 4].inject(0) { |result, element| result + element } # => 10
Ich schaue auf diesen Code, aber mein Gehirn registriert nicht, wie die Zahl 10 zum Ergebnis werden kann. Würde es jemandem etwas ausmachen zu erklären, was hier passiert?
[1, 2, 3, 4].inject(0) { |result, element| result + element } # => 10
Ich schaue auf diesen Code, aber mein Gehirn registriert nicht, wie die Zahl 10 zum Ergebnis werden kann. Würde es jemandem etwas ausmachen zu erklären, was hier passiert?
Antworten:
Sie können sich das erste Blockargument als Akkumulator vorstellen: Das Ergebnis jedes Blocklaufs wird im Akkumulator gespeichert und dann an die nächste Ausführung des Blocks übergeben. Im Fall des oben gezeigten Codes setzen Sie das Ergebnis des Akkumulators standardmäßig auf 0. Jeder Lauf des Blocks addiert die angegebene Zahl zur aktuellen Summe und speichert das Ergebnis dann wieder im Akkumulator. Der nächste Blockaufruf hat diesen neuen Wert, fügt ihn hinzu, speichert ihn erneut und wiederholt ihn.
Am Ende des Prozesses gibt inj den Akkumulator zurück, in diesem Fall die Summe aller Werte im Array oder 10.
Hier ist ein weiteres einfaches Beispiel zum Erstellen eines Hash aus einem Array von Objekten, die durch ihre Zeichenfolgendarstellung gekennzeichnet sind:
[1,"a",Object.new,:hi].inject({}) do |hash, item|
hash[item.to_s] = item
hash
end
In diesem Fall setzen wir unseren Akkumulator standardmäßig auf einen leeren Hash und füllen ihn dann jedes Mal auf, wenn der Block ausgeführt wird. Beachten Sie, dass wir den Hash als letzte Zeile des Blocks zurückgeben müssen, da das Ergebnis des Blocks wieder im Akkumulator gespeichert wird.
result + explanation
ist sowohl die Transformation zum Akkumulator als auch der Rückgabewert. Es ist die letzte Zeile im Block, die eine implizite Rückgabe darstellt.
inject
Nimmt zunächst einen Wert (den 0
in Ihrem Beispiel) und einen Block und führt diesen Block einmal für jedes Element der Liste aus.
result + element
) gespeichert .Der einfachste Weg, dies zu erklären, besteht darin, für Ihr Beispiel zu zeigen, wie jeder Schritt funktioniert. Dies ist eine imaginäre Reihe von Schritten, die zeigen, wie dieses Ergebnis bewertet werden kann:
[1, 2, 3, 4].inject(0) { |result, element| result + element }
[2, 3, 4].inject(0 + 1) { |result, element| result + element }
[3, 4].inject((0 + 1) + 2) { |result, element| result + element }
[4].inject(((0 + 1) + 2) + 3) { |result, element| result + element }
[].inject((((0 + 1) + 2) + 3) + 4) { |result, element| result + element }
(((0 + 1) + 2) + 3) + 4
10
Die Syntax für die Inject-Methode lautet wie folgt:
inject (value_initial) { |result_memo, object| block }
Lassen Sie uns das obige Beispiel lösen, dh
[1, 2, 3, 4].inject(0) { |result, element| result + element }
das gibt die 10 als Ausgabe.
Bevor wir beginnen, wollen wir uns ansehen, welche Werte in den einzelnen Variablen gespeichert sind:
Ergebnis = 0 Die Null kam von injizieren (Wert), was 0 ist
element = 1 Es ist das erste Element des Arrays.
Okey !!! Beginnen wir also mit dem Verständnis des obigen Beispiels
Schritt 1 [1, 2, 3, 4].inject(0) { |0, 1| 0 + 1 }
Schritt 2 [1, 2, 3, 4].inject(0) { |1, 2| 1 + 2 }
Schritt 3 [1, 2, 3, 4].inject(0) { |3, 3| 3 + 3 }
Schritt 4 [1, 2, 3, 4].inject(0) { |6, 4| 6 + 4 }
Schritt: 5 [1, 2, 3, 4].inject(0) { |10, Now no elements left in the array, so it'll return 10 from this step| }
Hier sind fett-kursive Werte Elemente, die aus dem Array abgerufen werden, und die einfach fett gedruckten Werte sind die resultierenden Werte.
Ich hoffe, dass Sie die Funktionsweise der #inject
Methode der verstehen #ruby
.
Der Code durchläuft die vier Elemente im Array und fügt dem aktuellen Element das vorherige Ergebnis hinzu:
Was sie gesagt haben, aber beachten Sie auch, dass Sie nicht immer einen "Startwert" angeben müssen:
[1, 2, 3, 4].inject(0) { |result, element| result + element } # => 10
ist das gleiche wie
[1, 2, 3, 4].inject { |result, element| result + element } # => 10
Probieren Sie es aus, ich werde warten.
Wenn kein Argument zum Injizieren übergeben wird, werden die ersten beiden Elemente an die erste Iteration übergeben. Im obigen Beispiel ist das Ergebnis 1 und das Element beim ersten Mal 2, sodass der Block weniger aufgerufen wird.
Die Zahl, die Sie in Ihr () der Injektion eingeben, stellt einen Startplatz dar. Sie kann 0 oder 1000 sein. In den Rohren befinden sich zwei Platzhalter | x, y |. x = welche Zahl auch immer Sie in der .inject ('x') hatten, und die Sekunde repräsentiert jede Iteration Ihres Objekts.
[1, 2, 3, 4].inject(5) { |result, element| result + element } # => 15
1 + 5 = 6 2 + 6 = 8 3 + 8 = 11 11 + 4 = 15
Inject wendet den Block an
result + element
zu jedem Element im Array. Für das nächste Element ("Element") lautet der vom Block zurückgegebene Wert "Ergebnis". So wie Sie es genannt haben (mit einem Parameter), beginnt "Ergebnis" mit dem Wert dieses Parameters. Der Effekt addiert also die Elemente.
tldr; inject
unterscheidet sich map
in einem wichtigen Punkt: inject
Gibt den Wert der letzten Ausführung des Blocks zurück, während map
das Array zurückgegeben wird, über das iteriert wurde.
Darüber hinaus wurde der Wert jeder Blockausführung über den ersten Parameter ( result
in diesem Fall) an die nächste Ausführung übergeben, und Sie können diesen Wert (das (0)
Teil) initialisieren .
Ihr obiges Beispiel könnte folgendermaßen geschrieben werden map
:
result = 0 # initialize result
[1, 2, 3, 4].map { |element| result += element }
# result => 10
Gleicher Effekt, aber inject
hier prägnanter.
Sie werden häufig feststellen, dass eine Zuweisung im map
Block erfolgt, während eine Auswertung im inject
Block erfolgt.
Welche Methode Sie wählen, hängt vom gewünschten Umfang ab result
. Wann man es nicht benutzt, wäre ungefähr so:
result = [1, 2, 3, 4].inject(0) { |x, element| x + element }
Sie mögen wie alle sein: "Sieh mal, ich habe das alles nur in einer Zeile zusammengefasst", aber Sie haben auch vorübergehend Speicher x
als result
Arbeitsvariable zugewiesen , die nicht erforderlich war, da Sie bereits damit arbeiten mussten.
[1, 2, 3, 4].inject(0) { |result, element| result + element } # => 10
ist gleichbedeutend mit:
def my_function(r, e)
r+e
end
a = [1, 2, 3, 4]
result = 0
a.each do |value|
result = my_function(result, value)
end
[1, 2, 3, 4].inject(0) { |result, element| result + element } # => 10
Im Klartext durchlaufen Sie dieses Array ( [1,2,3,4]
). Sie werden dieses Array viermal durchlaufen, da es vier Elemente gibt (1, 2, 3 und 4). Die Inject-Methode hat 1 Argument (die Nummer 0), und Sie fügen dieses Argument dem 1. Element hinzu (0 + 1. Dies entspricht 1). 1 wird im "Ergebnis" gespeichert. Dann addieren Sie dieses Ergebnis (das 1 ist) zum nächsten Element (1 + 2. Dies ist 3). Dies wird nun als Ergebnis gespeichert. Mach weiter: 3 + 3 ist gleich 6. Und schließlich ist 6 + 4 gleich 10.
Dieser Code erlaubt nicht die Möglichkeit, keinen Startwert zu übergeben, kann aber helfen, zu erklären, was los ist.
def incomplete_inject(enumerable, result)
enumerable.each do |item|
result = yield(result, item)
end
result
end
incomplete_inject([1,2,3,4], 0) {|result, item| result + item} # => 10
Beginnen Sie hier und überprüfen Sie dann alle Methoden, die Blöcke benötigen. http://ruby-doc.org/core-2.3.3/Enumerable.html#method-i-inject
Ist es der Block, der Sie verwirrt oder warum Sie einen Wert in der Methode haben? Gute Frage. Was ist die Operatormethode dort?
result.+
Wie fängt es an?
#inject(0)
Können wir das machen?
[1, 2, 3, 4].inject(0) { |result, element| result.+ element }
Funktioniert das?
[1, 2, 3, 4].inject() { |result = 0, element| result.+ element }
Sie sehen, ich baue auf der Idee auf, dass es einfach alle Elemente des Arrays summiert und eine Zahl in dem Memo ergibt, das Sie in den Dokumenten sehen.
Sie können dies immer tun
[1, 2, 3, 4].each { |element| p element }
um zu sehen, wie die Aufzählung des Arrays durchlaufen wird. Das ist die Grundidee.
Es ist nur so, dass Sie durch Injizieren oder Reduzieren ein Memo oder einen Akku erhalten, der verschickt wird.
Wir könnten versuchen, ein Ergebnis zu erzielen
[1, 2, 3, 4].each { |result = 0, element| result + element }
aber nichts kommt zurück, so dass dies genauso verhält wie zuvor
[1, 2, 3, 4].each { |result = 0, element| p result + element }
im Elementinspektorblock.
Dies ist eine einfache und ziemlich leicht verständliche Erklärung:
Vergessen Sie den "Anfangswert", da er am Anfang etwas verwirrend ist.
> [1,2,3,4].inject{|a,b| a+b}
=> 10
Sie können das Obige wie folgt verstehen: Ich injiziere eine "Addiermaschine" zwischen 1,2,3,4. Das heißt, es ist 1 ♫ 2 ♫ 3 ♫ 4 und ♫ ist eine Addiermaschine, also ist es dasselbe wie 1 + 2 + 3 + 4 und es ist 10.
Sie können tatsächlich ein +
dazwischen spritzen :
> [1,2,3,4].inject(:+)
=> 10
und es ist so, als würde man ein +
zwischen 1,2,3,4 injizieren , was es zu 1 + 2 + 3 + 4 macht und es ist 10. Das :+
ist Rubys Art, +
in Form eines Symbols zu spezifizieren .
Dies ist recht einfach zu verstehen und intuitiv. Und wenn Sie Schritt für Schritt analysieren möchten, wie es funktioniert, ist es wie folgt: Nehmen Sie 1 und 2 und fügen Sie sie jetzt hinzu. Wenn Sie ein Ergebnis haben, speichern Sie es zuerst (das ist 3), und jetzt wird das nächste gespeichert Wert 3 und das Array-Element 3 durchlaufen den a + b-Prozess, der 6 ist, und speichern jetzt diesen Wert, und jetzt durchlaufen 6 und 4 den a + b-Prozess und sind 10. Sie tun dies im Wesentlichen
((1 + 2) + 3) + 4
und ist 10. Der "Anfangswert" 0
ist zunächst nur eine "Basis". In vielen Fällen brauchen Sie es nicht. Stellen Sie sich vor, Sie brauchen 1 * 2 * 3 * 4 und es ist
[1,2,3,4].inject(:*)
=> 24
und es ist geschafft. Sie brauchen keinen "Anfangswert" von 1
, um das Ganze mit zu multiplizieren 1
.
Es gibt eine andere Form der .inject () -Methode, die sehr hilfreich ist [4,5] .inject (&: +), die das gesamte Element des Bereichs addiert
Ist das gleiche wie das:
[1,2,3,4].inject(:+)
=> 10