tags:

views:

55

answers:

2

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?

+2  A: 

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
yjerem
Greg Campbell
How adorable!! -
yjerem
Thanks! That makes perfect sense now. In terms of efficiency, is the inject way better, since it doesn't cause an extra array to be built up? In this example, it doesn't matter much, but what if we were selecting 1000s of values from a much larger range?
Greg Charles
Yes, `inject` is much more efficient, but suffers from lack of readability I think. I'd go with Greg Campbell's suggestion. It combines the readability of my `select` example with the efficiency of `inject`.
yjerem
+2  A: 

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)}

pierr