2010-07-24 3 views
7

Je suis novice en Ruby et j'ai un problème étrange avec la méthode d'injection.Aucune classe lors de l'utilisation de l'injection Ruby

Quand je fais:

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

le résultat est 10, comme prévu. Mais quand je fais

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

Je reçois une erreur:

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 

Je ne comprends pas vraiment pourquoi (probablement) le nombre est nul dans le second exemple, mais pas la première. Dans tous les cas, comment pourrais-je compter de 1 à 10 en utilisant injecter?

Répondre

14

L'expression count + 1 if (x%2 == 0) renvoie nil lorsque la condition n'est pas vraie, ce qui est défini sur count car c'est la nature de la méthode d'injection.

Vous pouvez le réparer en retournant count + 1 quand il est un nombre pair et juste count quand il est pas:

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

Une solution complètement différente est d'utiliser select pour sélectionner les nombres pairs et utiliser la méthode Array#length compter leur.

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

Si vous utilisez Ruby 1.8.7+, vous pouvez également utiliser le nombre de Enumerable #, à savoir '(1..10) .count (&: même)' –

+0

Comment adorable !! - –

+0

Merci! Cela prend tout son sens maintenant. En termes d'efficacité, est-ce que l'injection est meilleure, puisqu'elle n'entraîne pas la construction d'un tableau supplémentaire? Dans cet exemple, peu importe, mais que se passerait-il si nous sélectionnions des milliers de valeurs dans une gamme beaucoup plus large? –

3

Comme yjerem déjà indiqué, count + 1 if (x%2 == 0) sera évalué à nil lorsque x est impair. Et, voici le problème: la valeur nil sera affectée à count, de sorte que l'itération suivante sera nil + 1, ce qui a causé l'erreur signalée.

Il est important de comprendre comment injecter des œuvres (une copie de 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).

Une règle vous tenir à l'écart de ce genre d'erreur: le bloc doit toujours retourner le même type de valeur que celle de l'accumulateur. Si votre exemple, le bloc renverra un type de nil lorsque x%2==0 si false.

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

Questions connexes