0

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
1
  • Just return memo in the last part of the ternary instead of using next. Also, with inject, the memo += s is identical to memo + s, so the equal sign is really redundant here. Commented Feb 8, 2017 at 1:34

4 Answers 4

7

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
Sign up to request clarification or add additional context in comments.

Comments

3

Use compact

array.compact.reduce(0,:+)

3 Comments

Use reduce(0, :+) otherwise if the array has zero non-nil elements this returns nil, not 0
Thanks, I didn't think about that edge case. I was assuming the array had nil elements
(array-[nil]).inject(:+) just for fun ))
1

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.

Comments

0

I figured it out:

array.reject{|a| a.nil?}.inject(0){|memo,a| memo+=a}

1 Comment

While it'll work it's slower, and will continue to get slower as the array size grows. You're better off using the built-in methods as demonstrated by stackoverflow.com/a/42093625/128421. Also, unless you intend to select your answer it's better to not add your "final" code as an answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.