Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Ruby complain of "undefined method `+' for nil:NilClass (NoMethodError)" only when I use an 'if' conditional

Tags:

ruby

debugging

I'm attempting Problem 6 in Project Euler in Ruby (in my attempt to learn the language), and here is what I came up with in the first iteration:

upto = 10
a = (1..upto).to_a.product((1..upto).to_a)
#a.each{ |x| print "(#{x[0]}, #{x[1]})\n"}
puts a.inject(0) {|sum, x| sum + x[0]*x[1] if (x[0] != x[1])}

Unfortunately, that throws up the following error on Ruby 2.0:

in block in <main>': undefined method +' for nil:NilClass (NoMethodError)

Even more puzzling is that the error is not encountered when I remove the if conditional (which obviously gives me the wrong answer btw!)

upto = 10
a = (1..upto).to_a.product((1..upto).to_a)
a.each{ |x| print "(#{x[0]}, #{x[1]})\n"}
puts a.inject(0) {|sum, x| sum + x[0]*x[1]} #if (x[0] != x[1])}

The above gives the following output (after printing out the elements of a):

3025

As a debugging step, I've even printed out the contents of 'a', to ensure that there are no nil elements - that has turned out fine. Could someone explain

  1. What is it that I'm doing wrong here?
  2. Why the difference when I leave out the 'if' conditional, as the error message is in the '+' operator which otherwise gets unconditionally executed?

EDIT: It would also be nice to get comments on alternative, more elegant ways to achieve the same solution, as I'd like to know the standard way a Rubyist would solve this!

like image 309
TCSGrad Avatar asked May 26 '13 08:05

TCSGrad


2 Answers

That's because you are passing nil in the case of x[0] != x[1] to the inject block. The return value of this block, is the new value of the accumulator value (sum), thus, if there's no change, just return sum. Otherwise the new value of sum is nil, for which nil.respond_to(:+) #=> false on the following iteration, leading to the error you've encountered.

n = 10
a = (1..n).to_a.product((1..n).to_a)
puts a.inject(0) {|sum, x| x[0] == x[1] ? sum + x[0] * x[1] : sum }
like image 60
Sirupsen Avatar answered Sep 24 '22 13:09

Sirupsen


As I'm sure you know, the value of sum is whatever the block evaluated to on the previous expression (or the initial value supplied on the first time)

What your code should do is

if x[0] != x[1]
  sum + x[0]*x[1]
else
  sum
end

Your code effectively omits the else, so when the condition is not met, your block evaluations to 0

You might also want to know that

(1..upto).combination(2).to_a

Gives you the array of non repeated pairs directly so that you don't need your if statement in inject

You could even do

(1..upto).to_a.combination(2).collect {|pair| 2* pair[0] * pair[1]}.inject(:+)
like image 42
Frederick Cheung Avatar answered Sep 26 '22 13:09

Frederick Cheung