8

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.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Nobita
  • 23,519
  • 11
  • 58
  • 87

4 Answers4

17

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
tokland
  • 66,169
  • 13
  • 144
  • 170
1

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}
sawa
  • 165,429
  • 45
  • 277
  • 381
0
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.

Kyle
  • 21,978
  • 2
  • 60
  • 61
  • 1
    `arr.map { |p| p[:goals].to_f / p[:number_of_matches] }.reduce(:+) / arr.size` would be a bit shorter (and not overflow the code div). – Niklas B. Mar 09 '12 at 19:05
  • Out of 93 characters in your one-liner, only 3 are spaces, and a few more around operators would make it far more readable. – Andrew Marshall Mar 09 '12 at 19:05
  • Niklas: you are mapping and then reducing, thus iterating over the array twice when only one pass is required. – Kyle Mar 09 '12 at 19:14
  • 1
    @Kyle: Yes, but a one-liner that does not fit the screen is not a one-liner. Usually code should wrap at about column 80. If performance were a concern, one would probably not choose Ruby for the job. Also, both solutions are `O(n)`, so it's not a "real" algorithmic difference. – Niklas B. Mar 09 '12 at 19:21
0

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
megas
  • 21,401
  • 12
  • 79
  • 130