olafsadventures
olafsadventures

Reputation: 151

Add numbers in mixed array

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

Answers (4)

Cary Swoveland
Cary Swoveland

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

tadman
tadman

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

mu is too short
mu is too short

Reputation: 434865

What you have doesn't work because:

  1. The value of memo += num if num.is_a?Integer is nil when num is not an Integer.
  2. The value of each block is fed to the next iteration as 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

Andrey Deineko
Andrey Deineko

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

Related Questions