Necesita una explicación simple del método de inyección

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

Estoy mirando este código, pero mi cerebro no está registrando cómo el número 10 puede convertirse en el resultado. ¿A alguien le importaría explicar lo que está pasando aquí?


fuente
3
Ver Wikipedia: Plegar (función de orden superior) : inyectar es un "pliegue a la izquierda", aunque (desafortunadamente) a menudo con efectos secundarios en el uso de Ruby.
user2864740

Respuestas:

208

Puede pensar en el primer argumento de bloque como un acumulador: el resultado de cada ejecución del bloque se almacena en el acumulador y luego se pasa a la siguiente ejecución del bloque. En el caso del código que se muestra arriba, está omitiendo el acumulador, resultado, a 0. Cada ejecución del bloque agrega el número dado al total actual y luego almacena el resultado nuevamente en el acumulador. La siguiente llamada de bloque tiene este nuevo valor, se agrega, lo almacena nuevamente y se repite.

Al final del proceso, inject devuelve el acumulador, que en este caso es la suma de todos los valores de la matriz, o 10.

Aquí hay otro ejemplo simple para crear un hash a partir de una matriz de objetos, clave por su representación de cadena:

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

En este caso, estamos predeterminando nuestro acumulador a un hash vacío, y luego lo llenamos cada vez que se ejecuta el bloque. Tenga en cuenta que debemos devolver el hash como la última línea del bloque, porque el resultado del bloque se almacenará nuevamente en el acumulador.

Drew Olson
fuente
Gran explicación, sin embargo, en el ejemplo dado por el OP, lo que se devuelve (como el hash está en su ejemplo). Termina con resultado + explicación y debería tener un valor de retorno, ¿sí?
Projjol
1
@Projjol the result + explanationes tanto la transformación al acumulador como el valor de retorno. Es la última línea del bloque, por lo que es un retorno implícito.
KA01
87

injecttoma un valor para comenzar (el 0en su ejemplo) y un bloque, y ejecuta ese bloque una vez para cada elemento de la lista.

  1. En la primera iteración, pasa el valor que proporcionó como valor inicial y el primer elemento de la lista, y guarda el valor que devolvió su bloque (en este caso result + element).
  2. Luego ejecuta el bloque nuevamente, pasando el resultado de la primera iteración como el primer argumento, y el segundo elemento de la lista como el segundo argumento, guardando nuevamente el resultado.
  3. Continúa de esta manera hasta que haya consumido todos los elementos de la lista.

La forma más fácil de explicar esto puede ser mostrar cómo funciona cada paso, por ejemplo; Este es un conjunto imaginario de pasos que muestran cómo se puede evaluar este resultado:

[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
fuente
Gracias por escribir los pasos. Esto ayudó mucho. Aunque estaba un poco confundido acerca de si quieres decir que el diagrama a continuación es cómo se implementa el método de inyección debajo en términos de lo que se pasa como argumentos para inyectar.
2
El siguiente diagrama se basa en cómo se podría implementar; no necesariamente se implementa exactamente de esta manera. Por eso dije que es un conjunto imaginario de pasos; demuestra la estructura básica, pero no la implementación exacta.
Brian Campbell el
27

La sintaxis para el método de inyección es la siguiente:

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

Vamos a resolver el ejemplo anterior, es decir

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

que da el 10 como salida.

Entonces, antes de comenzar, veamos cuáles son los valores almacenados en cada variable:

resultado = 0 El cero vino de inyectar (valor) que es 0

element = 1 Es el primer elemento de la matriz.

¡¡¡Bueno!!! Entonces, comencemos a entender el ejemplo anterior

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

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

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

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

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

Aquí los valores en negrita y cursiva son elementos que se obtienen de la matriz y los valores en negrita son los valores resultantes.

Espero que entiendas el funcionamiento del #injectmétodo de #ruby.

Vishal Nagda
fuente
19

El código itera sobre los cuatro elementos dentro de la matriz y agrega el resultado anterior al elemento actual:

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

Lo que dijeron, pero tenga en cuenta también que no siempre necesita proporcionar un "valor inicial":

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

es lo mismo que

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

Pruébalo, te espero.

Cuando no se pasa ningún argumento para inyectar, los dos primeros elementos se pasan a la primera iteración. En el ejemplo anterior, el resultado es 1 y el elemento es 2 la primera vez, por lo que se realiza una llamada menos al bloque.

Mike Woodhouse
fuente
14

El número que coloca dentro de su () de inyección representa un lugar inicial, podría ser 0 o 1000. Dentro de las tuberías tiene dos marcadores de posición | x, y |. x = cualquier número que tengas dentro del .inject ('x'), y el segundo representa cada iteración de tu objeto.

[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
fuente
6

Inject aplica el bloque

result + element

a cada elemento de la matriz. Para el siguiente elemento ("elemento"), el valor devuelto por el bloque es "resultado". Como lo ha llamado (con un parámetro), "resultado" comienza con el valor de ese parámetro. Entonces el efecto es sumar los elementos.

Jonathan Adelson
fuente
6

tldr; injectdifiere de mapuna manera importante: injectdevuelve el valor de la última ejecución del bloque, mientras que mapdevuelve la matriz sobre la que iteró.

Más que eso, el valor de cada ejecución de bloque pasó a la siguiente ejecución a través del primer parámetro ( resulten este caso) y puede inicializar ese valor (la (0)parte).

Su ejemplo anterior podría escribirse mapasí:

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

Mismo efecto pero injectes más conciso aquí.

A menudo encontrará que una tarea ocurre en el mapbloque, mientras que una evaluación ocurre en el injectbloque.

El método que elija depende del alcance que desee result. Cuándo no usarlo sería algo como esto:

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

Puede decir: "Mírame, acabo de combinar todo en una sola línea", pero también asignaste temporalmente la memoria xcomo una variable temporal que no era necesaria ya que ya tenías resultque trabajar.

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

es equivalente a lo siguiente:

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
fuente
3

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

En inglés simple, está pasando (iterando) a través de esta matriz ( [1,2,3,4]). Recorrerá esta matriz 4 veces, porque hay 4 elementos (1, 2, 3 y 4). El método de inyección tiene 1 argumento (el número 0), y agregará ese argumento al primer elemento (0 + 1. Esto equivale a 1). 1 se guarda en el "resultado". Luego agrega ese resultado (que es 1) al siguiente elemento (1 + 2. Esto es 3). Esto ahora se guardará como resultado. Continúa: 3 + 3 es igual a 6. Y finalmente, 6 + 4 es igual a 10.

Maddie
fuente
2

Este código no permite la posibilidad de no pasar un valor inicial, pero puede ayudar a explicar lo que está sucediendo.

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
fuente
1

Comience aquí y luego revise todos los métodos que toman bloques. http://ruby-doc.org/core-2.3.3/Enumerable.html#method-i-inject

¿Es el bloque lo que te confunde o por qué tienes un valor en el método? Buena pregunta sin embargo. ¿Cuál es el método del operador allí?

result.+

¿Cómo comienza?

#inject(0)

¿Podemos hacer esto?

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

¿Esto funciona?

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

Verá, estoy construyendo sobre la idea de que simplemente suma todos los elementos de la matriz y produce un número en la nota que ve en los documentos.

Siempre puedes hacer esto

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

para ver cómo se puede iterar el enumerable de la matriz. Esa es la idea básica.

Es solo que inyectar o reducir le da una nota o un acumulador que se envía.

Podríamos intentar obtener un resultado

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

pero nada regresa, así que esto actúa igual que antes

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

en el bloque inspector de elementos.

Douglas G. Allen
fuente
1

Esta es una explicación simple y bastante fácil de entender:

Olvídate del "valor inicial", ya que es algo confuso al principio.

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

Puede entender lo anterior como: Estoy inyectando una "máquina sumadora" entre 1,2,3,4. Es decir, es 1 ♫ 2 ♫ 3 ♫ 4 y ♫ es una máquina sumadora, por lo que es lo mismo que 1 + 2 + 3 + 4, y es 10.

De hecho, puede inyectar un +entre ellos:

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

y es como, inyectar un +entre 1,2,3,4, haciéndolo 1 + 2 + 3 + 4 y es 10. Esta :+es la forma de Ruby de especificar +en forma de un símbolo.

Esto es bastante fácil de entender e intuitivo. Y si desea analizar cómo funciona paso a paso, es como: tomar 1 y 2, y ahora agregarlos, y cuando tenga un resultado, guárdelo primero (que es 3), y ahora, el siguiente es el almacenado valor 3 y el elemento de matriz 3 que pasa por el proceso a + b, que es 6, y ahora almacena este valor, y ahora 6 y 4 pasan por el proceso a + b, y es 10. Usted esencialmente está haciendo

((1 + 2) + 3) + 4

y es 10. El "valor inicial" 0es solo una "base" para empezar. En muchos casos, no lo necesitas. Imagínese si necesita 1 * 2 * 3 * 4 y es

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

y ya está hecho. No necesita un "valor inicial" de 1para multiplicar todo 1.

nonopolaridad
fuente
0

Hay otra forma de método .inject () que es muy útil [4,5] .inject (&: +) que sumará todos los elementos del área

Hasanin Alsabounchi
fuente
0

Es justo reduceo fold, si estás familiarizado con otros idiomas.

Mella
fuente
-1

Es lo mismo que esto:

[1,2,3,4].inject(:+)
=> 10
Dijo Maadan
fuente
Si bien esto es real, no responde la pregunta.
Mark Thomas