Precisa de uma explicação simples do método de injeção

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

Estou vendo esse código, mas meu cérebro não está registrando como o número 10 pode se tornar o resultado. Alguém se importaria de explicar o que está acontecendo aqui?


fonte
3
Veja Wikipedia: Fold (função de ordem superior) : injetar é uma "dobra esquerda", embora (infelizmente) geralmente tenha efeitos colaterais no uso do Ruby.
precisa saber é o seguinte

Respostas:

208

Você pode pensar no primeiro argumento do bloco como um acumulador: o resultado de cada execução do bloco é armazenado no acumulador e depois passado para a próxima execução do bloco. No caso do código mostrado acima, você está padronizando o acumulador, resultado, para 0. Cada execução do bloco adiciona o número fornecido ao total atual e, em seguida, armazena o resultado novamente no acumulador. A próxima chamada em bloco tem esse novo valor, adiciona-o, armazena-o novamente e repete-o.

No final do processo, injetar retorna o acumulador, que neste caso é a soma de todos os valores na matriz ou 10.

Aqui está outro exemplo simples para criar um hash a partir de uma matriz de objetos, codificada por sua representação de string:

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

Nesse caso, estamos padronizando nosso acumulador para um hash vazio e preenchendo-o sempre que o bloco é executado. Observe que devemos retornar o hash como a última linha do bloco, porque o resultado do bloco será armazenado novamente no acumulador.

Drew Olson
fonte
ótima explicação, no entanto, no exemplo dado pelo OP, o que está sendo retornado (como o hash está no seu exemplo). Termina com resultado + explicação e deve ter um valor de retorno, sim?
precisa saber é o seguinte
1
@Projjol result + explanationé tanto a transformação no acumulador quanto o valor de retorno. É a última linha do bloco, tornando-o um retorno implícito.
KA01 15/09/16
87

injectpega um valor para começar ( 0no exemplo) e um bloco e executa esse bloco uma vez para cada elemento da lista.

  1. Na primeira iteração, ele passa o valor que você forneceu como valor inicial e o primeiro elemento da lista e salva o valor que seu bloco retornou (neste caso result + element).
  2. Em seguida, ele executa o bloco novamente, passando o resultado da primeira iteração como o primeiro argumento e o segundo elemento da lista como o segundo argumento, salvando novamente o resultado.
  3. Ele continua assim até consumir todos os elementos da lista.

A maneira mais fácil de explicar isso pode ser mostrar como cada etapa funciona, por exemplo; este é um conjunto imaginário de etapas, mostrando como esse resultado pode ser avaliado:

[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
fonte
Obrigado por escrever as etapas. Isso ajudou muito. Embora eu tenha ficado um pouco confuso sobre se você quer dizer que o diagrama abaixo é como o método injetar é implementado abaixo em termos do que é passado como argumento para injetar.
2
O diagrama abaixo é baseado em como ele pode ser implementado; não é necessariamente implementado exatamente dessa maneira. Por isso eu disse que é um conjunto imaginário de etapas; demonstra a estrutura básica, mas não a implementação exata.
27972 Brian Campbell
27

A sintaxe para o método injetar é a seguinte:

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

Vamos resolver o exemplo acima, ou seja

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

que dá o 10 como a saída.

Portanto, antes de começar, vamos ver quais são os valores armazenados em cada variável:

resultado = 0 O zero veio da injeção (valor) que é 0

elemento = 1 É o primeiro elemento da matriz.

Okey !!! Então, vamos começar a entender o exemplo acima

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

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

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

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

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

Aqui , os valores em negrito-itálico são elementos buscados na matriz e os valores em negrito são os valores resultantes.

Espero que você entenda o funcionamento do #injectmétodo da #ruby.

Vishal Nagda
fonte
19

O código itera sobre os quatro elementos na matriz e adiciona o resultado anterior ao elemento atual:

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

O que eles disseram, mas observe também que você nem sempre precisa fornecer um "valor inicial":

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

é o mesmo que

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

Experimente, eu espero.

Quando nenhum argumento é passado para injetar, os dois primeiros elementos são passados ​​para a primeira iteração. No exemplo acima, o resultado é 1 e o elemento é 2 na primeira vez, portanto, menos uma chamada é feita para o bloco.

Mike Woodhouse
fonte
14

O número que você coloca dentro do seu injetor () representa um ponto de partida, pode ser 0 ou 1000. Dentro dos tubos, você tem dois marcadores de posição | x, y |. x = qualquer número que você tenha dentro do .inject ('x'), e o segundo representa cada iteração do seu 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
fonte
6

Injetar aplica o bloco

result + element

para cada item na matriz. Para o próximo item ("elemento"), o valor retornado do bloco é "resultado". Da maneira que você chamou (com um parâmetro), "resultado" começa com o valor desse parâmetro. Portanto, o efeito é somar os elementos.

Jonathan Adelson
fonte
6

tldr; injectdifere de mapuma maneira importante: injectretorna o valor da última execução do bloco, enquanto mapretorna o array que iterou.

Mais do que isso, o valor de cada execução do bloco passou para a próxima execução pelo primeiro parâmetro ( resultneste caso) e você pode inicializar esse valor (a (0)parte).

Seu exemplo acima pode ser escrito usando o mapseguinte:

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

O mesmo efeito, mas inject é mais conciso aqui.

Você encontrará frequentemente uma atribuição no mapbloco, enquanto uma avaliação ocorre noinject bloco.

O método escolhido depende do escopo desejado result. Quando não usar, seria algo como isto:

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

Você pode ser como tudo: "Olha só, eu acabei de combinar tudo isso em uma linha", mas você também alocou temporariamente a memória xcomo uma variável temporária que não era necessária, pois você já tinha resultque trabalhar.

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

é equivalente ao seguinte:

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

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

Em inglês simples, você está passando (iterando) por essa matriz ( [1,2,3,4]). Você irá percorrer essa matriz 4 vezes, porque existem 4 elementos (1, 2, 3 e 4). O método injetar possui 1 argumento (o número 0) e você adicionará esse argumento ao 1º elemento (0 + 1. Isso é igual a 1). 1 é salvo no "resultado". Em seguida, adicione esse resultado (que é 1) ao próximo elemento (1 + 2. Isso é 3). Este vai agora ser salvo como o resultado. Continue: 3 + 3 é igual a 6. E, finalmente, 6 + 4 é igual a 10.

Maddie
fonte
2

Esse código não permite a possibilidade de não passar um valor inicial, mas pode ajudar a explicar o que está acontecendo.

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

Comece aqui e revise todos os métodos que aceitam blocos. http://ruby-doc.org/core-2.3.3/Enumerable.html#method-i-inject

É o bloco que confunde você ou por que você tem um valor no método? Boa pergunta embora. Qual é o método do operador lá?

result.+

Como é que começa?

#inject(0)

Nós podemos fazer isso?

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

Isto funciona?

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

Você vê que estou desenvolvendo a ideia de que simplesmente soma todos os elementos da matriz e gera um número no memorando que você vê nos documentos.

Você sempre pode fazer isso

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

para ver o enumerável da matriz ser iterado. Essa é a ideia básica.

É apenas que injetar ou reduzir fornecem um memorando ou um acumulador que é enviado.

Poderíamos tentar obter um resultado

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

mas nada volta, então isso funciona da mesma maneira que antes

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

no bloco inspetor de elementos.

Douglas G. Allen
fonte
1

Esta é uma explicação simples e bastante fácil de entender:

Esqueça o "valor inicial", pois é um pouco confuso no começo.

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

Você pode entender o que foi dito acima: Estou injetando uma "máquina de adicionar" entre 1,2,3,4. Ou seja, é 1 ♫ 2 ♫ 3 ♫ 4 e ♫ é uma máquina de somar, portanto é igual a 1 + 2 + 3 + 4 e é 10.

Você pode realmente injetar um +entre eles:

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

e é como, injetar um +entre 1,2,3,4, tornando-o 1 + 2 + 3 + 4 e é 10. :+É a maneira de Ruby especificar+ na forma de um símbolo.

Isso é muito fácil de entender e intuitivo. E se você deseja analisar como funciona passo a passo, é como: pegar 1 e 2, e agora adicioná-los, e quando você tiver um resultado, armazene-o primeiro (que é 3) e agora, a seguir é o armazenado o valor 3 e o elemento da matriz 3 passando pelo processo a + b, que é 6, e agora armazena esse valor, e agora 6 e 4 passam pelo processo a + b, e é 10. Você está fazendo essencialmente

((1 + 2) + 3) + 4

e é 10. O "valor inicial" 0é apenas uma "base" para começar. Em muitos casos, você não precisa disso. Imagine se você precisar de 1 * 2 * 3 * 4 e é

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

e está feito. Você não precisa de um "valor inicial" 1para multiplicar a coisa toda 1.

falta de polaridade
fonte
0

Existe outra forma de método .inject () Isso é muito útil [4,5] .inject (&: +) Isso adicionará todo o elemento da área

Hasanin Alsabounchi
fonte
0

É apenas reduceou fold, se você estiver familiarizado com outros idiomas.

usuario
fonte
-1

É o mesmo que isto:

[1,2,3,4].inject(:+)
=> 10
Said Maadan
fonte
Embora isso seja factual, não responde à pergunta.
Mark Thomas