Benötigen Sie eine einfache Erklärung der Injektionsmethode

142
[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?


quelle
3
Siehe Wikipedia: Fold (Funktion höherer Ordnung) : Inject ist eine "Fold Left", obwohl (leider) häufig Nebenwirkungen bei der Verwendung von Ruby auftreten.
user2864740

Antworten:

208

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.

Drew Olson
quelle
Eine gute Erklärung ist jedoch, dass in dem vom OP gegebenen Beispiel zurückgegeben wird, was zurückgegeben wird (wie Hash in Ihrem Beispiel). Es endet mit Ergebnis + Erklärung und sollte einen Rückgabewert haben, ja?
Projjol
1
@Projjol das result + explanationist sowohl die Transformation zum Akkumulator als auch der Rückgabewert. Es ist die letzte Zeile im Block, die eine implizite Rückgabe darstellt.
KA01
87

injectNimmt zunächst einen Wert (den 0in Ihrem Beispiel) und einen Block und führt diesen Block einmal für jedes Element der Liste aus.

  1. Bei der ersten Iteration wird der von Ihnen als Startwert angegebene Wert und das erste Element der Liste übergeben und der von Ihrem Block zurückgegebene Wert (in diesem Fall result + element) gespeichert .
  2. Anschließend wird der Block erneut ausgeführt, wobei das Ergebnis der ersten Iteration als erstes Argument und das zweite Element aus der Liste als zweites Argument übergeben werden, wobei das Ergebnis erneut gespeichert wird.
  3. Dies wird so fortgesetzt, bis alle Elemente der Liste verbraucht sind.

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
Brian Campbell
quelle
Vielen Dank, dass Sie die Schritte ausgeschrieben haben. Das hat sehr geholfen. Obwohl ich ein wenig verwirrt war, ob Sie damit meinen, dass das folgende Diagramm zeigt, wie die Inject-Methode darunter implementiert wird, was als Argumente für das Injizieren übergeben wird.
2
Das folgende Diagramm basiert auf , wie es könnte umgesetzt werden; es ist nicht unbedingt genau so implementiert. Deshalb habe ich gesagt, es ist eine imaginäre Reihe von Schritten; Es zeigt die Grundstruktur, aber nicht die genaue Implementierung.
Brian Campbell
27

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 #injectMethode der verstehen #ruby.

Vishal Nagda
quelle
19

Der Code durchläuft die vier Elemente im Array und fügt dem aktuellen Element das vorherige Ergebnis hinzu:

  • 1 + 2 = 3
  • 3 + 3 = 6
  • 6 + 4 = 10
John Topley
quelle
15

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.

Mike Woodhouse
quelle
14

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

Stuart G.
quelle
6

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.

Jonathan Adelson
quelle
6

tldr; injectunterscheidet sich mapin einem wichtigen Punkt: injectGibt den Wert der letzten Ausführung des Blocks zurück, während mapdas Array zurückgegeben wird, über das iteriert wurde.

Darüber hinaus wurde der Wert jeder Blockausführung über den ersten Parameter ( resultin 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 injecthier prägnanter.

Sie werden häufig feststellen, dass eine Zuweisung im mapBlock erfolgt, während eine Auswertung im injectBlock 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 xals resultArbeitsvariable zugewiesen , die nicht erforderlich war, da Sie bereits damit arbeiten mussten.

IAmNaN
quelle
4
[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
Fred Willmore
quelle
3

[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.

Maddie
quelle
2

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
Andrew Grimm
quelle
1

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.

Douglas G. Allen
quelle
1

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" 0ist 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.

Unpolarität
quelle
0

Es gibt eine andere Form der .inject () -Methode, die sehr hilfreich ist [4,5] .inject (&: +), die das gesamte Element des Bereichs addiert

Hasanin Alsabounchi
quelle
0

Es ist nur reduceoder fold, wenn Sie mit anderen Sprachen vertraut sind.

Nick
quelle
-1

Ist das gleiche wie das:

[1,2,3,4].inject(:+)
=> 10
Sagte Maadan
quelle
Dies ist zwar sachlich, beantwortet aber nicht die Frage.
Mark Thomas