Hai bisogno di una semplice spiegazione del metodo di iniezione


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

Sto guardando questo codice ma il mio cervello non sta registrando come il numero 10 può diventare il risultato. Qualcuno potrebbe spiegare cosa sta succedendo qui?

ruby  syntax 

3
Vedi Wikipedia: Fold (funzione di ordine superiore) : inject è una "piega a sinistra", sebbene (purtroppo) spesso con effetti collaterali nell'uso di Ruby.
user2864740

Risposte:


208

Puoi considerare il primo argomento del blocco come un accumulatore: il risultato di ogni corsa del blocco viene memorizzato nell'accumulatore e quindi passato alla successiva esecuzione del blocco. Nel caso del codice mostrato sopra, si sta impostando l'accumulatore per impostazione predefinita, risultato, su 0. Ogni esecuzione del blocco aggiunge il numero dato al totale corrente e quindi memorizza il risultato nell'accumulatore. La prossima chiamata di blocco ha questo nuovo valore, lo aggiunge, lo memorizza di nuovo e si ripete.

Alla fine del processo, iniettare restituisce l'accumulatore, che in questo caso è la somma di tutti i valori nell'array o 10.

Ecco un altro semplice esempio per creare un hash da una matrice di oggetti, codificato dalla loro rappresentazione in formato stringa:

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

In questo caso, stiamo impostando il nostro accumulatore su un hash vuoto, quindi lo popoliamo ogni volta che il blocco viene eseguito. Si noti che è necessario restituire l'hash come ultima riga del blocco, poiché il risultato del blocco verrà memorizzato nuovamente nell'accumulatore.


grande spiegazione, tuttavia, nell'esempio fornito dall'OP, cosa viene restituito (come l'hash è nel tuo esempio). Termina con risultato + spiegazione e dovrebbe avere un valore di ritorno, sì?
Projjol,

1
@Projjol the result + explanationè sia la trasformazione in accumulatore che il valore di ritorno. È l'ultima riga nel blocco che lo rende un ritorno implicito.
KA01,

87

injectprende un valore per iniziare ( 0nel tuo esempio) e un blocco, e lo esegue una volta per ogni elemento dell'elenco.

  1. Alla prima iterazione, passa il valore fornito come valore iniziale e il primo elemento dell'elenco e salva il valore restituito dal blocco (in questo caso result + element).
  2. Quindi esegue nuovamente il blocco, passando il risultato dalla prima iterazione come primo argomento e il secondo elemento dall'elenco come secondo argomento, salvando nuovamente il risultato.
  3. Continua in questo modo fino a quando non ha consumato tutti gli elementi dell'elenco.

Il modo più semplice per spiegare questo può essere quello di mostrare come funziona ogni passaggio, per esempio; questo è un insieme immaginario di passaggi che mostrano come questo risultato potrebbe essere valutato:

[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

Grazie per aver scritto i passaggi. Questo ha aiutato molto. Anche se ero un po 'confuso sul fatto che tu intenda che il diagramma sotto è come il metodo di iniezione è implementato sotto in termini di ciò che è passato come argomenti da iniettare.

2
Lo schema seguente si basa su come potrebbe essere implementato; non è necessariamente implementato esattamente in questo modo. Ecco perché ho detto che è una serie di passaggi immaginari; dimostra la struttura di base, ma non l'implementazione esatta.
Brian Campbell,

27

La sintassi per il metodo di iniezione è la seguente:

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

Risolviamo l'esempio sopra cioè

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

che dà il 10 come output.

Quindi, prima di iniziare vediamo quali sono i valori memorizzati in ciascuna variabile:

risultato = 0 Lo zero proviene dall'iniezione (valore) che è 0

element = 1 È il primo elemento dell'array.

Okey !!! Quindi, iniziamo a capire l'esempio sopra

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

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

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

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

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

Qui i valori in grassetto corsivo sono elementi recuperati dall'array e i valori semplicemente in grassetto sono i valori risultanti.

Spero che tu capisca il funzionamento del #injectmetodo di #ruby.


19

Il codice scorre i quattro elementi all'interno dell'array e aggiunge il risultato precedente all'elemento corrente:

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

15

Quello che hanno detto, ma nota anche che non è sempre necessario fornire un "valore iniziale":

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

equivale a

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

Provalo, aspetterò.

Quando non viene passato alcun argomento da iniettare, i primi due elementi vengono passati alla prima iterazione. Nell'esempio sopra, il risultato è 1 e l'elemento è 2 la prima volta, quindi viene effettuata una chiamata in meno al blocco.


14

Il numero che inserisci all'interno di () di inject rappresenta un punto iniziale, potrebbe essere 0 o 1000. All'interno dei tubi hai due segnaposti | x, y |. x = qualunque numero tu abbia mai avuto all'interno di .inject ('x'), e il secondo rappresenta ogni iterazione del tuo oggetto.

[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 applica il blocco

result + element

a ciascun elemento dell'array. Per l'elemento successivo ("elemento"), il valore restituito dal blocco è "risultato". Il modo in cui l'hai chiamato (con un parametro), "risultato" inizia con il valore di quel parametro. Quindi l'effetto sta sommando gli elementi.


6

TLDR; injectdifferisce mapin un modo importante: injectrestituisce il valore dell'ultima esecuzione del blocco mentre maprestituisce l'array su cui è ripetuto.

Oltre a ciò, il valore di ogni esecuzione di blocco è passato all'esecuzione successiva tramite il primo parametro ( resultin questo caso) e puoi inizializzare quel valore (la (0)parte).

L'esempio sopra potrebbe essere scritto usando in mapquesto modo:

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

Stesso effetto ma injectqui è più conciso.

Troverai spesso un'assegnazione nel mapblocco, mentre una valutazione avviene nel injectblocco.

Il metodo scelto dipende dall'ambito desiderato result. Quando non usarlo sarebbe qualcosa del genere:

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

Potresti essere come tutti "Senti, ho appena combinato tutto in una riga", ma hai anche temporaneamente allocato la memoria xcome variabile scratch che non era necessaria dal momento che dovevi già resultlavorare.


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

è equivalente al seguente:

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

In parole povere, stai attraversando (ripetendo) questo array ( [1,2,3,4]). Esaminerai questo array 4 volte, perché ci sono 4 elementi (1, 2, 3 e 4). Il metodo inject ha 1 argomento (il numero 0) e aggiungerai quell'argomento al 1 ° elemento (0 + 1. Questo equivale a 1). 1 viene salvato nel "risultato". Quindi aggiungi quel risultato (che è 1) all'elemento successivo (1 + 2. Questo è 3). Questo verrà ora salvato come risultato. Continua: 3 + 3 è uguale a 6. E infine, 6 + 4 è uguale a 10.


2

Questo codice non consente di non passare un valore iniziale, ma può aiutare a spiegare cosa sta succedendo.

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

Inizia qui e rivedi tutti i metodi che richiedono blocchi. http://ruby-doc.org/core-2.3.3/Enumerable.html#method-i-inject

È il blocco che ti confonde o perché hai un valore nel metodo? Buona domanda però. Qual è il metodo dell'operatore lì?

result.+

Come inizia?

#inject(0)

Possiamo fare questo?

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

funziona?

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

Vedi, mi sto basando sull'idea che somma semplicemente tutti gli elementi dell'array e produce un numero nel memo che vedi nei documenti.

Puoi sempre farlo

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

per visualizzare l'enumerabile dell'array. Questa è l'idea di base.

È solo che l'iniezione o la riduzione ti danno un memo o un accumulatore che viene inviato.

Potremmo provare a ottenere un risultato

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

ma non torna nulla, quindi questo si comporta come prima

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

nel blocco ispettore elemento.


1

Questa è una spiegazione semplice e abbastanza facile da capire:

Dimentica il "valore iniziale" in quanto è un po 'confuso all'inizio.

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

Puoi capire quanto sopra come: sto iniettando una "macchina addizionatrice" tra 1,2,3,4. Significa che è 1 ♫ 2 ♫ 3 ♫ 4 e ♫ è una macchina addizionatrice, quindi è uguale a 1 + 2 + 3 + 4, ed è 10.

Puoi effettivamente iniettare una +tra di loro:

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

ed è come, iniettare +a tra 1,2,3,4, rendendolo 1 + 2 + 3 + 4 ed è 10. È :+il modo di Ruby di specificare +nella forma di un simbolo.

Questo è abbastanza facile da capire e intuitivo. E se vuoi analizzare come funziona passo dopo passo, è come: prendere 1 e 2, e ora aggiungerli, e quando hai un risultato, memorizzalo prima (che è 3), e ora, poi viene memorizzato valore 3 e l'elemento dell'array 3 che attraversa il processo a + b, che è 6, e ora memorizza questo valore, e ora 6 e 4 passano attraverso il processo a + b, ed è 10. Stai essenzialmente facendo

((1 + 2) + 3) + 4

ed è 10. Il "valore iniziale" 0è solo una "base" per cominciare. In molti casi, non ne hai bisogno. Immagina se hai bisogno di 1 * 2 * 3 * 4 e lo è

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

ed è fatto. Non è necessario un "valore iniziale" 1per moltiplicare il tutto con 1.


0

Esiste un'altra forma di metodo .inject () che è molto utile [4,5] .inject (&: +) che sommerà tutti gli elementi dell'area


0

È solo reduceo fold, se hai familiarità con altre lingue.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.