Summing odd integers from an Array in Ruby

143 views Asked by At
inputs = [
  ["9", "5", "4"],
  ["20", "40", "60"],
  ["1", "3", "19"]
]
numbers = inputs.sample
pp numbers

I’m trying to figure out how to add only the odd integers in the array together. So whenever I run the test, it’ll choose from the 3 rows and add them together.

I attempted to do it with an .each method to loop each integer but I keep running into errors.

sum = 0
numbers.each  { |num| sum = num}
if  pp num.to_i.odd?
    pp num.to_i 
end
4

There are 4 answers

8
smathy On

Based on comment clarification:

inputs.sample.map(&:to_i).select(&:odd?).sum

For giggles, this is the fastest method I could find:

inputs.sample.map(&:to_i).sum {|e| e * ( e % 2 )}
0
engineersmnky On

Ruby Array has a method called sum.

You can pass a block to this method and it will "sum" (quotes are because Array#sum is not limited to the mathematical definition of sum) up the result of that block so in your case this can be boiled down to:

inputs = [
  ["9", "5", "4"],
  ["20", "40", "60"],
  ["1", "3", "19"]
]
inputs.sample.sum {|e| e.to_i.odd? ? e.to_i : 0 } 

In this case (using the ternary operator) if e.to_i is an odd number then we return that number otherwise we return 0. This equates to the following for each know input:

  • ["9", "5", "4"] -> [9,5,0].sum
  • ["20", "40", "60"] -> [0,0,0].sum
  • ["1", "3", "19"] -> [1,3,19].sum

There are umpteen other ways to accomplish this goal in Ruby using any number of Enumerable methods. (e.g. map, select, filter_map, inject, etc.)

4
Casper On

If we go into a shooting match on speed, I think this is even faster as it sidesteps an unnecessary to_i conversion in case the number is even.

inputs.sample.sum { |n| n.getbyte(-1).odd? ? n.to_i : 0 } }

Running a benchmark on all the supplied answers yields the following:

require 'benchmark'
require 'benchmark/ips'

nums = 100.times.map { rand(100).to_s }

Benchmark.ips do |x|
  x.report('simple') { nums.map(&:to_i).select(&:odd?).sum  }
  x.report('fast')   { nums.map(&:to_i).sum { |e| e * ( e % 2 ) } }
  x.report('odd')    { nums.sum { |e| e.to_i.odd? ? e.to_i : 0 } }
  x.report('ascii')  { nums.sum { |n| n.getbyte(-1).odd? ? n.to_i : 0 } }
  x.compare!
end

Output:

ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [x86_64-linux]
Warming up --------------------------------------
              simple     2.873k i/100ms
                fast     2.706k i/100ms
                 odd     3.753k i/100ms
               ascii     4.090k i/100ms
Calculating -------------------------------------
              simple     21.038k (±22.9%) i/s -    100.555k in   5.013970s
                fast     28.255k (±26.4%) i/s -    132.594k in   5.003179s
                 odd     35.939k (±25.9%) i/s -    168.885k in   5.064407s
               ascii     56.739k (±20.7%) i/s -    269.940k in   5.013879s

Comparison:
               ascii:    56739.0 i/s
                 odd:    35939.2 i/s - same-ish: difference falls within error
                fast:    28255.1 i/s - 2.01x  slower
              simple:    21038.4 i/s - 2.70x  slower
0
Stefan On

There are several problems with your attempt.

First of all, sum = num will assign num to sum, thus overwriting its previous value:

sum = 0
sum = "9"
sum #=> "9"

sum = "5"
sum #=> "5"

It's not enough to name the variable sum, you have to add num to sum in order to get an actual sum. This can be done via sum = sum + 9 (add 9 to the current value of sum and assign the result back to sum) or the shorthand sum += 9. However, since num is – despite its name – a string, you have to convert it to an integer beforehand:

sum = 0
sum += "9".to_i
sum #=> 9

sum += "5".to_i
sum #=> 14

Inside a loop:

sum = 0
nums.each do |num|
  sum += num.to_i
end

Next, your condition if num.to_i.odd? is executed after the loop and it won't affect the sum in any way. You should move it inside the loop to only add the numbers that are odd:

sum = 0
nums.each do |num|
  if num.to_i.odd?
    sum += num.to_i
  end
end

There's also a modifier-if which can be appended to an expression:

sum = 0
nums.each do |num|
  sum += num.to_i if num.to_i.odd?
end

And you could also use a so-called guard clause to skip elements that aren't odd via next:

sum = 0
nums.each do |num|
  next unless num.to_i.odd?

  sum += num.to_i
end

A more structured approach would be to first map all strings to their integer counterparts, to select the odd ones and to finally sum them using the built-in sum method:

nums = ["9", "5", "4"]

nums.map { |num| num.to_i }    #=> [9, 5, 4]
    .select { |num| num.odd? } #=> [9, 5]
    .sum                       #=> 14

Which can be shortened to:

nums.map(&:to_i).select(&:odd?).sum #=> 14