2010-07-24 2 views
7

Я новичок в Ruby, и у меня возникла странная проблема с методом инъекции.Класс Nil при использовании Ruby injection

Когда я делаю:

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

результат 10, как и ожидалось. Но когда я

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

Я получаю сообщение об ошибке:

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 

Я не понимаю, почему (предположительно) граф равен нулю во втором примере, но не первый. В любом случае, как я буду считать evens от 1 до 10, используя инъекцию?

ответ

14

Выражение count + 1 if (x%2 == 0) возвращает nil, когда условие не соответствует действительности, которое устанавливается count, потому что это характер метода инъекции.

Вы можете это исправить, вернув count + 1, когда это четное число, и только count, когда это не так:

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

Совершенно иное решение использовать select для выбора четных чисел и использовать метод Array#length сосчитать их.

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

Если вы используете Ruby, 1.8.7+, вы можете также использовать Enumerable подсчет #, т.е. '(1..10) .count (&: даже)' –

+0

Как очаровательны !! - –

+0

Спасибо! Теперь это имеет смысл. С точки зрения эффективности, лучше ли внедрить путь, так как он не создает лишний массив? В этом примере это не имеет большого значения, но что, если мы выбираем 1000 значений из гораздо большего диапазона? –

3

Как yjerem уже отмечалось, count + 1 if (x%2 == 0) будет оцениваться на nil при х нечетно. И здесь проблема: значение nil будет присвоено count, поэтому следующая итерация будет nil + 1, что вызвало ошибку.

Важно понять, как впрыснуть работы (копия из 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).

Правило будет держать вас от такого рода ошибок: блок должен всегда возвращать один и тот же тип значения, что и аккумулятор. Если ваш пример, блок вернет тип nil, если x%2==0, если false.

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

Смежные вопросы