Besoin d'une explication simple de la méthode d'injection


142
[1, 2, 3, 4].inject(0) { |result, element| result + element } # => 10

Je regarde ce code mais mon cerveau n'enregistre pas comment le nombre 10 peut devenir le résultat. Quelqu'un voudrait-il expliquer ce qui se passe ici?

ruby  syntax 

3
Voir Wikipedia: Fold (fonction d'ordre supérieur) : inject est un "repli à gauche", bien que (malheureusement) souvent avec des effets secondaires dans l'utilisation de Ruby.
user2864740

Réponses:


208

Vous pouvez considérer le premier argument de bloc comme un accumulateur: le résultat de chaque exécution du bloc est stocké dans l'accumulateur puis passé à la prochaine exécution du bloc. Dans le cas du code ci-dessus, vous réglez par défaut l'accumulateur, résultat, sur 0. Chaque exécution du bloc ajoute le nombre donné au total actuel, puis stocke le résultat dans l'accumulateur. L'appel de bloc suivant a cette nouvelle valeur, y ajoute, le stocke à nouveau et se répète.

À la fin du processus, inject renvoie l'accumulateur, qui dans ce cas est la somme de toutes les valeurs du tableau, ou 10.

Voici un autre exemple simple pour créer un hachage à partir d'un tableau d'objets, indexé par leur représentation sous forme de chaîne:

[1,"a",Object.new,:hi].inject({}) do |hash, item|
  hash[item.to_s] = item
  hash
end

Dans ce cas, nous définissons par défaut notre accumulateur sur un hachage vide, puis nous le remplissons à chaque fois que le bloc s'exécute. Remarquez que nous devons retourner le hachage comme dernière ligne du bloc, car le résultat du bloc sera stocké dans l'accumulateur.


grande explication, cependant, dans l'exemple donné par l'OP, ce qui est retourné (comme le hachage est dans votre exemple). Il se termine par résultat + explication et devrait avoir une valeur de retour, oui?
Projjol le

1
@Projjol the result + explanationest à la fois la transformation en accumulateur et la valeur de retour. C'est la dernière ligne du bloc, ce qui en fait un retour implicite.
KA01 du

87

injectprend une valeur pour commencer (le 0dans votre exemple), et un bloc, et il exécute ce bloc une fois pour chaque élément de la liste.

  1. Lors de la première itération, il transmet la valeur que vous avez fournie comme valeur de départ et le premier élément de la liste, et il enregistre la valeur renvoyée par votre bloc (dans ce cas result + element).
  2. Il exécute ensuite à nouveau le bloc, en transmettant le résultat de la première itération comme premier argument et le deuxième élément de la liste comme deuxième argument, enregistrant à nouveau le résultat.
  3. Il continue ainsi jusqu'à ce qu'il ait consommé tous les éléments de la liste.

La manière la plus simple d'expliquer cela peut être de montrer comment chaque étape fonctionne, par exemple; il s'agit d'un ensemble imaginaire d'étapes montrant comment ce résultat pourrait être évalué:

[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

Merci d'avoir écrit les étapes. Cela a beaucoup aidé. Bien que je sois un peu confus quant à savoir si vous voulez dire que le diagramme ci-dessous montre comment la méthode inject est implémentée en dessous en termes de ce qui est passé en tant qu'arguments à injecter.

2
Le diagramme ci-dessous est basé sur la façon dont il pourrait être mis en œuvre; il n'est pas nécessairement mis en œuvre exactement de cette façon. C'est pourquoi j'ai dit que c'était un ensemble imaginaire d'étapes; il montre la structure de base, mais pas la mise en œuvre exacte.
Brian Campbell

27

La syntaxe de la méthode inject est la suivante:

inject (value_initial) { |result_memo, object| block }

Résolvons l'exemple ci-dessus ie

[1, 2, 3, 4].inject(0) { |result, element| result + element }

ce qui donne le 10 comme sortie.

Donc, avant de commencer, voyons quelles sont les valeurs stockées dans chaque variable:

result = 0 Le zéro provient de inject (valeur) qui est 0

element = 1 C'est le premier élément du tableau.

Bien!!! Alors, commençons à comprendre l'exemple ci-dessus

Étape 1 [1, 2, 3, 4].inject(0) { |0, 1| 0 + 1 }

Étape 2 [1, 2, 3, 4].inject(0) { |1, 2| 1 + 2 }

Étape 3 [1, 2, 3, 4].inject(0) { |3, 3| 3 + 3 }

Étape 4 [1, 2, 3, 4].inject(0) { |6, 4| 6 + 4 }

Étape: 5 [1, 2, 3, 4].inject(0) { |10, Now no elements left in the array, so it'll return 10 from this step| }

Ici, les valeurs Bold-Italic sont des éléments extraits du tableau et les valeurs simplement Bold sont les valeurs résultantes.

J'espère que vous comprenez le fonctionnement de la #injectméthode du #ruby.


19

Le code itère sur les quatre éléments du tableau et ajoute le résultat précédent à l'élément actuel:

  • 1 + 2 = 3
  • 3 + 3 = 6
  • 6 + 4 = 10

15

Ce qu'ils ont dit, mais notez également que vous n'avez pas toujours besoin de fournir une "valeur de départ":

[1, 2, 3, 4].inject(0) { |result, element| result + element } # => 10

est le même que

[1, 2, 3, 4].inject { |result, element| result + element } # => 10

Essayez-le, j'attendrai.

Lorsqu'aucun argument n'est passé à injecter, les deux premiers éléments sont passés dans la première itération. Dans l'exemple ci-dessus, le résultat est 1 et l'élément est 2 la première fois, donc un appel de moins est effectué vers le bloc.


14

Le nombre que vous mettez dans votre () d'inject représente un point de départ, il pourrait être 0 ou 1000. À l'intérieur des tuyaux, vous avez deux espaces réservés | x, y |. x = quel que soit le nombre que vous aviez dans le .inject ('x'), et la seconde représente chaque itération de votre objet.

[1, 2, 3, 4].inject(5) { |result, element| result + element } # => 15

1 + 5 = 6 2 + 6 = 8 3 + 8 = 11 11 + 4 = 15


6

Inject applique le bloc

result + element

à chaque élément du tableau. Pour l'élément suivant ("élément"), la valeur renvoyée par le bloc est "résultat". La façon dont vous l'avez appelé (avec un paramètre), "result" commence par la valeur de ce paramètre. Donc, l'effet est d'ajouter les éléments.


6

tldr; injectdiffère d' mapune manière importante: injectrenvoie la valeur de la dernière exécution du bloc tandis que mapretourne le tableau sur lequel il a itéré.

Plus que cela, la valeur de chaque exécution de bloc est passée à l'exécution suivante via le premier paramètre ( resultdans ce cas) et vous pouvez initialiser cette valeur (la (0)partie).

Votre exemple ci-dessus pourrait être écrit en utilisant mapcomme ceci:

result = 0 # initialize result
[1, 2, 3, 4].map { |element| result += element }
# result => 10

Même effet mais injectest plus concis ici.

Vous constaterez souvent qu'une affectation se produit dans le mapbloc, tandis qu'une évaluation se produit dans le injectbloc.

La méthode que vous choisissez dépend de la portée que vous souhaitez result. Quand ne pas l' utiliser, ce serait quelque chose comme ceci:

result = [1, 2, 3, 4].inject(0) { |x, element| x + element }

Vous pourriez être comme tout le monde, "Regardez-moi, je viens de combiner tout cela en une seule ligne", mais vous avez également temporairement alloué de la mémoire pour xune variable de scratch qui n'était pas nécessaire puisque vous deviez déjà resulttravailler avec.


4
[1, 2, 3, 4].inject(0) { |result, element| result + element } # => 10

équivaut à ce qui suit:

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

3

[1, 2, 3, 4].inject(0) { |result, element| result + element } # => 10

En clair, vous parcourez (itérer) ce tableau ( [1,2,3,4]). Vous parcourrez ce tableau 4 fois, car il y a 4 éléments (1, 2, 3 et 4). La méthode inject a 1 argument (le nombre 0), et vous ajouterez cet argument au 1er élément (0 + 1. Cela équivaut à 1). 1 est enregistré dans le "résultat". Ensuite, vous ajoutez ce résultat (qui est 1) à l'élément suivant (1 + 2. C'est 3). Cela sera maintenant enregistré comme résultat. Continuez: 3 + 3 égale 6. Et enfin, 6 + 4 égale 10.


2

Ce code ne permet pas de ne pas transmettre de valeur de départ, mais peut aider à expliquer ce qui se passe.

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

1

Commencez ici, puis passez en revue toutes les méthodes qui prennent des blocs. http://ruby-doc.org/core-2.3.3/Enumerable.html#method-i-inject

Est-ce le bloc qui vous déroute ou pourquoi vous avez une valeur dans la méthode? Bonne question cependant. Quelle est la méthode opérateur là-bas?

result.+

Comment cela commence-t-il?

#inject(0)

Pouvons-nous faire cela?

[1, 2, 3, 4].inject(0) { |result, element| result.+ element }

Est-ce que ça marche?

[1, 2, 3, 4].inject() { |result = 0, element| result.+ element }

Vous voyez que je m’appuie sur l’idée que cela résume simplement tous les éléments du tableau et donne un nombre dans le mémo que vous voyez dans la documentation.

Tu peux toujours faire ça

 [1, 2, 3, 4].each { |element| p element }

pour voir l'énumérable du tableau être itéré. C'est l'idée de base.

C'est juste qu'injecter ou réduire vous donne un mémo ou un accumulateur qui est envoyé.

On pourrait essayer d'obtenir un résultat

[1, 2, 3, 4].each { |result = 0, element| result + element }

mais rien ne revient donc ça agit de la même manière qu'avant

[1, 2, 3, 4].each { |result = 0, element| p result + element }

dans le bloc inspecteur d'élément.


1

C'est une explication simple et assez facile à comprendre:

Oubliez la «valeur initiale» car elle est quelque peu déroutante au début.

> [1,2,3,4].inject{|a,b| a+b}
=> 10

Vous pouvez comprendre ce qui précède comme suit: J'injecte une "machine à ajouter" entre 1,2,3,4. Cela signifie que c'est 1 ♫ 2 ♫ 3 ♫ 4 et ♫ est une machine à additionner, donc c'est la même chose que 1 + 2 + 3 + 4, et c'est 10.

Vous pouvez en fait injecter un +entre eux:

> [1,2,3,4].inject(:+)
=> 10

et c'est comme, injecter un +entre 1, 2, 3, 4, ce qui en fait 1 + 2 + 3 + 4 et c'est 10. C'est :+la manière de Ruby de spécifier +sous la forme d'un symbole.

C'est assez facile à comprendre et intuitif. Et si vous voulez analyser son fonctionnement étape par étape, c'est comme: prendre 1 et 2, et maintenant les ajouter, et quand vous avez un résultat, stockez-le d'abord (qui est 3), et maintenant, le suivant est le stocké valeur 3 et l'élément de tableau 3 passant par le processus a + b, qui est 6, et stockent maintenant cette valeur, et maintenant 6 et 4 passent par le processus a + b, et est 10. Vous faites essentiellement

((1 + 2) + 3) + 4

et vaut 10. La "valeur initiale" 0est juste une "base" pour commencer. Dans de nombreux cas, vous n'en avez pas besoin. Imaginez si vous avez besoin de 1 * 2 * 3 * 4 et c'est

[1,2,3,4].inject(:*)
=> 24

et c'est fait. Vous n'avez pas besoin d'une "valeur initiale" de 1pour multiplier le tout avec 1.


0

Il existe une autre forme de méthode .inject () qui est très utile [4,5] .inject (&: +) Cela ajoutera tous les éléments de la zone


0

C'est juste reduceou fold, si vous connaissez d'autres langues.


-1

Est-ce le même que ceci:

[1,2,3,4].inject(:+)
=> 10

Bien que ce soit factuel, cela ne répond pas à la question.
Mark Thomas
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.