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.

5
  • You might want to fix your array/hash so that it's actually valid Ruby. Commented Mar 9, 2012 at 18:45
  • Sorry, I get a JSON and I map it to a hash. Let me edit that. Commented Mar 9, 2012 at 18:46
  • 2
    One-liners are interesting, but often overrated, IMO. I think asking for an elegant and clean solution is better than asking for a one-liner. Commented Mar 9, 2012 at 19:02
  • @Andrew: Agreed, especially because this doesn't seem to be a problem that can be solved elegantly in one libe. Commented Mar 9, 2012 at 19:04
  • @AndrewMarshall You are right. I will take that into account when formulating this kind of questions to avoid confusion in answers. Commented Mar 9, 2012 at 19:05

4 Answers 4

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

6 Comments

Kyle: Doing this in one line will require code repetition or inaccurate results.
Niklas: code repetition? Please explain how my solution is repeating code? It makes one pass over the array and produces the result.
@Kyle: I said or inaccurate results, like your solution, which sums up the individual division errors. If at all, you'd have to use Rational.
one-liner solution will lead to inaccurate result on big numbers, please look at my variant
@Kyle: Just replace every line break with a semicolon. Voilà: one-liner.
|
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}

1 Comment

Heh, nice trick with instance_eval :) I'd rather not see that in production code, though :P
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.

4 Comments

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).
Out of 93 characters in your one-liner, only 3 are spaces, and a few more around operators would make it far more readable.
Niklas: you are mapping and then reducing, thus iterating over the array twice when only one pass is required.
@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.
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

Comments

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.