I want to add the numbers in the array and ignore the nil elements. My solution obviously doesn't work.
array = [3,4,5,6,nil,9,nil,9,3]
def add(array)
array.inject(0){|memo,s|s.is_a?Integer ? memo+=s : next}
end
The reason why your version doesn't work is simple: the return value of the block becomes the memo value for the next iteration. In the case that s is not an Integer, you simply return nothing, i.e. nil, so during the next iteration, memo is nil.
You can fix it by simply returning memo unchanged:
def add(array)
array.inject(0) { |memo, s| s.is_a? Integer ? memo += s : next memo }
# ↑↑↑↑
end
This is the smallest possible change that will fix your problem.
A block normally returns the value of the last expression evaluated inside the block, so the next is actually not necessary:
def add(array)
array.inject(0) { |memo, s| s.is_a? Integer ? memo += s : memo }
end
It doesn't make sense to assign to memo, it goes out of scope at the end of the block anyway, and you never use it again:
def add(array)
array.inject(0) { |memo, s| s.is_a? Integer ? memo + s : memo }
end
According to the standard community style guides, argument lists to message sends should be enclosed in parenthesis, unless it's a "procedure-like" message send (e.g. puts, require) or a "keyword-like" one (e.g. attr_accessor):
def add(array)
array.inject(0) { |memo, s| s.is_a?(Integer) ? memo + s : memo }
end
Personally, I don't like the conditional operator. That's a stylistic issue, though, but I much prefer this:
def add(array)
array.inject(0) { |memo, s| if s.is_a?(Integer) then memo + s else memo end }
end
However, a much easier solution is to remove the nil elements before you iterate:
def add(array)
array.reject {|s| s.nil? }.inject(0) { |memo, s| memo + s }
end
Which can be more elegantly written using Symbol#to_proc
def add(array)
array.reject(&:nil?).inject(0) { |memo, s| memo + s }
end
But is actually just equivalent to the more readable:
def add(array)
array.compact.inject(0) { |memo, s| memo + s }
end
inject allows you to pass the name of the combining method as a Symbol:
def add(array)
array.compact.inject(0, :+)
end
And that is just the same as sum:
def add(array)
array.compact.sum
end
Use compact
array.compact.reduce(0,:+)
reduce(0, :+) otherwise if the array has zero non-nil elements this returns nil, not 0(array-[nil]).inject(:+) just for fun ))You can do it many ways:
array = [3,4,5,6,nil,9,nil,9,3]
array.sum(&:to_i) # => 39
array.reject(&:nil?).sum # => 39
You can use compact instead of reject(&:nil?):
array.compact.sum # => 39
If you don't have sum available, which you should if you're using a reasonably recent Ruby, you can use inject(:+):
array.compact.inject(:+) # => 39
require 'fruity'
compare do
sum_to_i { array.sum(&:to_i) }
reject_nil { array.reject(&:nil?).sum }
compact_reduce { array.compact.reduce(0,:+) }
compact_sum { array.compact.sum }
compact_inject { array.compact.inject(:+) }
do_not_do_this { array.reject{|a| a.nil?}.inject(0){|memo,a| memo+=a} }
end
# >> Running each test 8192 times. Test will take about 1 second.
# >> compact_sum is faster than compact_inject by 10.000000000000009% ± 10.0%
# >> compact_inject is similar to compact_reduce
# >> compact_reduce is faster than sum_to_i by 19.999999999999996% ± 10.0%
# >> sum_to_i is faster than reject_nil by 1.9x ± 0.1
# >> reject_nil is faster than do_not_do_this by 2.0x ± 0.1
Jörg's solution is clearly the fastest.
I figured it out:
array.reject{|a| a.nil?}.inject(0){|memo,a| memo+=a}
memoin the last part of the ternary instead of usingnext. Also, with inject, thememo += sis identical tomemo + s, so the equal sign is really redundant here.