1

How can I return the total scores, strokes and rounds from the following array?

players = [{"Angel Cabrera"=>{"score"=>2, "strokes"=>146, "rounds"=>3}},
 {"Jason Day"=>{"score"=>1, "strokes"=>145, "rounds"=>3}},
 {"Bryson DeChambeau"=>{"score"=>0, "strokes"=>144, "rounds"=>3}},
 {"Sergio Garcia"=>{"score"=>0, "strokes"=>144, "rounds"=>3}},
 {"Ian Poulter"=>{"score"=>5, "strokes"=>162, "rounds"=>3}},
 {"Vijay Singh"=>nil},
 {"Jordan Spieth"=>{"score"=>-4, "strokes"=>140, "rounds"=>3}}]

I can get the strokes by doing the following but I know that isn't the best way to do it.

  players.each do |x|
    x.values()[0]["strokes"]
  end

How can I return the sum of the strokes given the array above?

1
  • If you are not given the object players, it may be better to represent it as a hash: { "Angel Cabrera"=>{"score"=>2, "strokes"=>146, "rounds"=>3}, ...}. You should explain what you want to do with elements of players that contain a value of nil. I assumed they are to be skipped, but initially did not notice the lone nil, which resulted in a bug in my code. Commented Apr 9, 2016 at 18:20

2 Answers 2

2

Here are three ways of doing that.

Use the form of Hash#update that employs a block to determine the values of keys that are present in both hashes being merged

players.map { |g| g.first.last }.
        compact.
        each_with_object({}) { |g,h| h.update(g) { |_,o,v| o+v } }
  #=> {"score"=>4, "strokes"=>881, "rounds"=>18}

The steps:

a = players.map { |g| g.first.last }
  #=> [{"score"=> 2, "strokes"=>146, "rounds"=>3},
  #    {"score"=> 1, "strokes"=>145, "rounds"=>3},
  #    {"score"=> 0, "strokes"=>144, "rounds"=>3},
  #    {"score"=> 0, "strokes"=>144, "rounds"=>3},
  #    {"score"=> 5, "strokes"=>162, "rounds"=>3},
  #    nil,
  #    {"score"=>-4, "strokes"=>140, "rounds"=>3}] 
b = a.compact
  #=> [{"score"=> 2, "strokes"=>146, "rounds"=>3},
  #    {"score"=> 1, "strokes"=>145, "rounds"=>3},
  #    {"score"=> 0, "strokes"=>144, "rounds"=>3},
  #    {"score"=> 0, "strokes"=>144, "rounds"=>3},
  #    {"score"=> 5, "strokes"=>162, "rounds"=>3},
  #    {"score"=>-4, "strokes"=>140, "rounds"=>3}] 
b.each_with_object({}) { |g,h| h.update(g) { |_,o,v| o+v } }
  #=> {"score"=>4, "strokes"=>881, "rounds"=>18}    

Here, Hash#update (aka merge!) uses the block { |_,o,v| o+v }) to determine the values of keys that are present in both hashes. The first block variable (which is not used, and therefore can be represented by the local variable _) is the key, the second (o, for "old") is the value of the key in h and the third (n, for "new") is the value of the key in g.

Use a counting hash

players.map { |g| g.first.last }.
        compact.
        each_with_object(Hash.new(0)) { |g,h| g.keys.each { |k| h[k] += g[k] } }

Hash.new(0) creates an empty hash with a default value of zero, represented by the block variable g. This means that if a hash h does not have a key k, h[k] returns the default value (but does not alter the hash). h[k] += g[k] above expands to:

h[k] = h[k] + g[k]

If h does not have a key k, h[k] on the right side is therefore replaced by 0.

Sum values and then convert to a hash

If you are using Ruby v1.9+ and the keys are guaranteed to have the same order in each hash, a third way it could be done is as follows:

["scores", "strokes", "rounds"].zip(
  players.map { |g| g.first.last }.
          compact.
          map(&:values).
          transpose.
          map { |arr| arr.reduce(:+) }
  ).to_h
  #=> {"scores"=>4, "strokes"=>881, "rounds"=>18}

The steps (starting from b above) are:

c = b.map(&:values)
  #=> [[ 2, 146, 3],
  #    [ 1, 145, 3],
  #    [ 0, 144, 3],
  #    [ 0, 144, 3],
  #    [ 5, 162, 3],
  #    [-4, 140, 3]] 
d = c.transpose
  #=> [[  2,   1,   0,   0,   5,  -4],
  #    [146, 145, 144, 144, 162, 140],
  #    [  3,   3,   3,   3,   3,   3]] 
totals = d.map { |arr| arr.reduce(:+) }
  #=> [4, 881, 18]
e = ["scores", "strokes", "rounds"].zip(totals)
  #=> [["scores", 4], ["strokes", 881], ["rounds", 18]]
e.to_h
  #=> {"scores"=>4, "strokes"=>881, "rounds"=>18} 
Sign up to request clarification or add additional context in comments.

Comments

2

Use this code:

@total= 0
players.each do |x|
a= x.values[0]
if a.class == Hash
  @total += a["strokes"]
end
end

puts @total

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.