Nobita
Nobita

Reputation: 23713

How do I calculate the average values in objects located in an array?

Let's say I have an array like this:

[
  {
    "player_id"         => 1,
    "number_of_matches" => 2,
    "goals"             => 5
  },
  {
    "player_id"         => 2,
    "number_of_matches" => 4,
    "goals"             => 10
  }
]

I want to have the average goals per match among all the players, not the average for each individual player, but the total average.

I have in mind doing it with .each and storing each of the individual averages, and at the end add them all and divide by the number of players I have. However, I am looking for a Ruby/ one-liner way of doing this.

Upvotes: 8

Views: 4803

Answers (4)

tokland
tokland

Reputation: 67850

As requested, a one-liner:

avg = xs.map { |x| x["goals"].to_f / x["number_of_matches"] }.reduce(:+) / xs.size

A more readable snippet:

goals, matches = xs.map { |x| [x["goals"], x["number_of_matches"]] }.transpose 
avg = goals.reduce(:+).to_f / matches.reduce(:+) if goals

Upvotes: 17

megas
megas

Reputation: 21791

To make string shorter, lets rename "number_of_matches" to "matches"

a = [
  {"player_id":1 , "matches":2, "goals": 5}, 
  {"player_id":2 , "matches":4, "goals": 10}
]

a.reduce([0,0]){|sum,h|[sum.first+h["goals"],sum.last+h["matches"]]}.reduce{|sum,m|sum.to_f/m}
#=> 2.5

Upvotes: 0

Kyle
Kyle

Reputation: 22258

a = [{player_id:1 , match_num:2, goals: 5}, {player_id:2 , match_num:4, goals: 10}]

a.reduce(0){|avg, p| avg += p[:goals].to_f/p[:match_num]}/a.size

Edit: renamed keys and block args to reduce char count. For those who care.

First, your keys need to use => if your going to use strings as keys.

reduce will iterate over the array and sum the individual averages for each player and finally we divide that result by the number of total players. The '0' in the parenthesis is your starting number for reduce.

Upvotes: 0

sawa
sawa

Reputation: 168071

A slight modification to tokland's answer.

items.map{|e| e.values_at("goals", "number_of_matches")}.transpose.map{|e| e.inject(:+)}.instance_eval{|goals, matches| goals.to_f/matches}

Upvotes: 1

Related Questions