Reputation: 176
I'm currently learning ruby and I wrote this piece of code :
def multi_gen
s = []
for i in (3..10)
if i%3 == 0 || i%5 == 0
s<<i
end
end
return s
end
puts multi_gen
def rec_sum(num_arr)
if num_arr == []
return 0
else
num_arr.first + rec_sum(num_arr.shift)
end
end
puts rec_sum(multi_gen)
That should return the sum of all 3 and 5 multiples up to 1000.
But I get an error :
myrbfile.rb:17:in `rec_sum': undefined method `first' for 3:Fixnum (NoMethodError)
from villani.rb:17:in `rec_sum'
from villani.rb:21:in `<main>'
But when I re-write it like this :
def multi_gen
s = []
for i in (3..10)
if i%3 == 0 || i%5 == 0
s<<i
end
end
return s
end
puts multi_gen
def rec_sum(num_arr)
if num_arr == []
return 0
else
num_arr[0] + rec_sum(num_arr[1..num_arr.last])
end
end
puts rec_sum(multi_gen)
I don't get the error.
So why is my first rec_sum functions interpretting my Array as a Fixnum in the first case?
Upvotes: 1
Views: 66
Reputation: 114138
mudasobwa already explained why using shift
doesn't give the expected result. Apart from that, your code is somehow unidiomatic.
In multi_gen
you are creating an empty array and append elements to it using a for
loop. You rarely have to populate an array manually. Instead, you can usually use one of Ruby's Array
or Enumerable
methods to generate the array. select
is a very common one – it returns an array containing the elements for which the given block returns true
:
(1..1000).select { |i| i % 3 == 0 || i % 5 == 0 }
#=> [3, 5, 6, 9, 10, 12, ...]
In rec_sum
, you check if num_arr == []
. Although this works, you are creating an empty throw-away array. To determine whether an array is empty, you should call its empty?
:
if num_arr.empty?
# ...
end
To get the remaining elements from the array, you use:
num_arr[1..num_arr.last]
which can be abbreviated by passing a negative index to []
:
num_arr[1..-1]
There's also drop
which might look a little nicer:
num_arr[0] + rec_sum(num_arr[1..-1])
# vs
num_arr.first + rec_sum(num_arr.drop(1))
Another option to get first and remaining elements from an array is Ruby's array decomposition feature (note the *
):
def rec_sum(num_arr)
if num_arr.empty?
0
else
first, *remaining = num_arr
first + rec_sum(remaining)
end
end
You could also consider using a guard clause to return from the method early:
def rec_sum(num_arr)
return 0 if num_arr.empty?
first, *remaining = num_arr
first + rec_sum(remaining)
end
Writing recursive methods is great for learning purposed, but Ruby also has a built-in sum
method:
multi_gen.sum #=> 234168
or – since you are using an older Ruby version – inject
:
multi_gen.inject(0, :+) #=> 234168
Upvotes: 0
Reputation: 121000
The issue is in the recursive call:
rec_sum(num_arr.shift)
Array#shift
returns the shifted element, not the remaining array. You should explicitly pass the array as an argument to recursive call:
rec_sum(num_arr[1..-1])
or
rec_sum(num_arr.tap(&:shift))
The latter would [likely] be looking too cumbersome for the beginner, but it’s a very common rubyish approach: Object#tap
yields the receiver to the block, returning the receiver. Inside a block (num_arr.tap(&:shift)
is a shorthand for num_arr.tap { |a| a.shift }
we mutate the array by shifting the element out, and it’s being returned as a result.
Upvotes: 2