2010-07-24 10 views
7

Sono nuovo di Ruby e ho uno strano problema con il metodo inject.Classe Nil quando si utilizza l'iniezione di Ruby

Quando faccio:

(1..10).inject(0) {|count,x| count + 1} 

il risultato è 10, come previsto. Ma quando faccio

(1..10).inject(0) {|count,x| count + 1 if (x%2 == 0)} 

ottengo un errore:

NoMethodError: undefined method `+' for nil:NilClass 
    from (irb):43 
    from (irb):43:in `inject' 
    from (irb):43:in `each' 
    from (irb):43:in `inject' 
    from (irb):43 

Io non capisco il motivo per cui (presumibilmente) conteggio è pari a zero nel secondo esempio, ma non la prima. In ogni caso, come potrei contare le variabili da 1 a 10 usando l'iniezione?

risposta

14

L'espressione count + 1 if (x%2 == 0) restituisce nil quando la condizione non è vera, a cui viene impostato count perché è la natura del metodo di inserimento.

Si potrebbe risolvere il problema restituendo count + 1 quando si tratta di un numero pari e proprio count quando non lo è:

(1..10).inject(0) { |count,x| x % 2 == 0 ? count + 1 : count } 

Una soluzione completamente diversa è quella di utilizzare select per selezionare i numeri pari e utilizzare il metodo Array#length per contare loro.

(1..10).select { |x| x % 2 == 0 }.length 
+1

Se stai usando Rubino 1.8.7+, si potrebbe anche usare conteggio Enumerable #, vale a dire (1..10) .count (e?: Anche) '' –

+0

Come adorabile !! - –

+0

Grazie! Questo ha perfettamente senso ora. In termini di efficienza, il modo inietti è migliore, dal momento che non provoca la creazione di un array aggiuntivo? In questo esempio, non importa molto, ma cosa succede se selezionassimo 1000s di valori da una gamma molto più ampia? –

3

Come yjerem già sottolineato, count + 1 if (x%2 == 0) saranno valutate al nil quando x è dispari. E, ecco il problema: il valore nil verrà assegnato a count, quindi l'iterazione successiva sarà nil + 1, che ha causato l'errore segnalato.

E 'importante capire come iniettare opere (una copia da ruby-doc)

enum.inject(initial) {| memo, obj | block } => obj

enum.inject {| memo, obj | block } => obj

Combines the elements of enum by applying the block to an accumulator value (memo) and each element in turn. At each step, memo is set to the value returned by the block. The first form lets you supply an initial value for memo. The second form uses the first element of the collection as a the initial value (and skips that element while iterating).

Una regola vi terrà lontano da questo tipo di errore: il blocco deve sempre tornare lo stesso tipo di valore come quello di l'accumulatore. Se il tuo esempio, il blocco restituirà un tipo di nil quando x%2==0 se false.

(1..10).inject(0) {|count,x| count + 1 if (x%2 == 0)}

Problemi correlati