Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nil class when using Ruby injection

Tags:

ruby

I'm new to Ruby, and I'm having a strange problem with the inject method.

When I do:

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

the result is 10, as expected. But when I do

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

I get an error:

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

I don't really understand why (presumably) count is nil in the second example, but not the first. In any case, how would I count evens from 1 to 10 using inject?

like image 596
Greg Charles Avatar asked Jul 24 '10 01:07

Greg Charles


2 Answers

The expression count + 1 if (x%2 == 0) returns nil when the condition isn't true, which count gets set to because that's the nature of the inject method.

You could fix it by returning count + 1 when it's an even number and just count when it's not:

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

A completely different solution is to use select to select the even numbers and use the Array#length method to count them.

(1..10).select { |x| x % 2 == 0 }.length
like image 155
Paige Ruten Avatar answered Nov 07 '22 21:11

Paige Ruten


As yjerem already pointed out, count + 1 if (x%2 == 0) will be evaluated to nil when x is odd. And , here is problem: the nil value will be assigned to count, so the next iteration will be nil + 1 ,which caused error reported.

It is important to understand how inject works(a copy from 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).

A rule will keep you away from this kind of error : the block should always return the same type of value as that of the accumulator. If your example, the block will return a type of nil when x%2==0 if false.

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

like image 39
pierrotlefou Avatar answered Nov 07 '22 20:11

pierrotlefou