Reputation: 151
My expectation for this code is the number 3. Why doesn't this work?
mixed_array=[1,'cat',2]
mixed_array.inject(0) do |memo,num|
memo += num if num.is_a?Integer
end
NoMethodError: undefined method `+' for nil:NilClass
Upvotes: 0
Views: 49
Reputation: 110735
mixed_array = [1, 'cat', 2, [3, 4], :a, { b: 5, c: 6 }, 7]
mixed_array.reduce(0) { |tot, obj| tot += Integer(obj) rescue 0 }
#=> 10
When the array may include one or more floats and you want to return a float:
mixed_array = [1, 'cat', 2, [3, 4], :a, { b: 5, c: 6 }, 7, 8.123]
mixed_array.reduce(0) { |tot, obj| tot += Float(obj) rescue 0 }
#=> 18.122999999999998
See Kernel::Integer and Kernel::Float.
Upvotes: 0
Reputation: 211690
You can do this a lot more easily if you consider it as a two stage operation rather than one:
mixed_array=[1,'cat',2]
mixed_array.grep(Integer).inject(0, :+)
# => 3
This filters out all the non-Integer elements from the array and adds the rest together.
Remember that inject
takes the return value from the previous iteration as the seed for the next. Your if
clause must return an alternate value. You end up with this if you fix it:
memo += num.is_a?(Integer) ? num : 0
You could also go with a good-enough solution like:
memo += num.to_i
Depending on what sort of data you're trying to screen out.
Upvotes: 0
Reputation: 434865
What you have doesn't work because:
memo += num if num.is_a?Integer
is nil
when num
is not an Integer
.memo
.Your block evaluates to nil
on the second iteration so you're going to end up trying to evaluate:
nil += 2 if 2.is_a? Integer
and there's your NoMethodError.
You're probably better off doing this in two steps for clarity:
mixed_array.select { |e| e.is_a? Integer }.inject(:+)
or maybe even the looser version:
mixed_array.select { |e| e.is_a? Numeric }.inject(:+)
or with newer versions of Ruby:
mixed_array.select { |e| e.is_a? Numeric }.sum
If you're not dogmatically opposed to ternaries then you could also say things like:
mixed_array.inject(0) { |memo, num| memo + (num.is_a?(Integer) ? num : 0) }
mixed_array.sum { |e| e.is_a?(Integer) ? e : 0 }
If you know that the non-numeric elements of mixed_array
are strings that don't look like numbers or start with numbers (i.e. nothing like '0'
, '11 pancakes'
, ...) then you could say:
mixed_array.map(&:to_i).inject(:+)
mixed_array.inject(0) { |memo, num| memo + num.to_i }
...
but that's probably making too many assumptions.
Upvotes: 0
Reputation: 52367
You were almost there:
mixed_array.inject(0) do |memo, num|
next memo unless num.is_a?(Integer)
memo + num
end
#=> 3
Making your code working:
mixed_array.inject(0) do |memo, num|
memo += num if num.is_a?(Integer)
memo # return memo on each iteration, because it becomes nil with non-integer element
end
#=> 3
Upvotes: 1