jthedudeoflife
jthedudeoflife

Reputation: 391

Convert Ruby Array into average

Say I have an array as such:

array = [["male", 59], ["female", 31], ["unknown gender", 38]] 

The number inside the array is the total number of users by gender. What I need to do is get each element of the array and find the average for each male, female and unknown and put them into a new array that looks like this:

new_array = [["male", 46], ["female", 24], ["unknown gender", 30]] 

Where the 2nd value inside the tuple is the average.

I have tried setting it as such:

new_array = []
sum = 0
array.each do |k,v|
sum += v
new_array << [k,(v/sum)*100]
end

My new array however turns out to be [["male", 100], ["female", 0], ["unknown gender", 0]]

Ive tried a few different things and cannot figure out what Im doing wrong. Any help would be great.

Upvotes: 0

Views: 108

Answers (4)

Cary Swoveland
Cary Swoveland

Reputation: 110645

If you have a choice, I suggest you consider using hashes rather than an arrays and symbols rather than strings:

hash = { male: 59, female: 31, unknown_gender: 38 }
  #=> {:male=>59, :female=>31, :unknown_gender=>38}
tot = hash.values.reduce(:+)
  #=> 128
percents = hash.each_with_object({}) { |(k,v),h| h[k] = (100.0*v/tot).round }
  #=> {:male=>46, :female=>24, :unknown_gender=>30}

Upvotes: 0

spickermann
spickermann

Reputation: 106782

I would prefer to use more specialised Ruby methods like inject or map instead of the simple each:

array = [['male', 59], ['female', 31], ['unknown gender', 38]] 

total = array.inject(0) { |sum, (_, n)| sum + n }

new_array = array.map { |k, v| [k, ((v.to_f / total) * 100).round] }
#=> [["male", 46], ["female", 24], ["unknown gender", 30]]

Furthermore the ((v.to_f / total) * 100).round part can be extracted into its own method like this:

def percentage(value, total)
  ((value.to_f / total) * 100).round
end

What makes the map more readable:

new_array = array.map { |k, v| [k, percentage(v, total)] }

Upvotes: 0

August
August

Reputation: 12558

There are two issues here.

The first is that you are doing integer division at (v/sum). The result of this is probably not what you expect, so you should turn v into a float: (v.to_f/sum).

The second problem is that you are using the sum variable before it actually contains the sum.

Putting these two together:

array = [["male", 59], ["female", 31], ["unknown gender", 38]] 
new_array = []
sum = 0
# calculating sum before it's used in division below.
array.each { |_, v| sum += v }

array.each do |k, v|
  new_array << [k, ((v.to_f / sum) * 100).round]
end
# => [["male", 46], ["female", 24], ["unknown gender", 30]]

Upvotes: 1

ruby_newbie
ruby_newbie

Reputation: 3275

You need to do a few things. what is happening now is that sum is 59. v/59 is one on the first iteration. On the second iteration 31/90 == 0 because you are not dividing the sum by the number of iterations. What you really want is 31/45 because 45 is 90/2(2 being the number of iterations)

1) you need to divide the sum by the number of iterations to get the current average.

2) your syntax of

array.each do |k,v|

is misleading because you are not dealing with keys and values but with array indexes.

Upvotes: 0

Related Questions