0

I have an array of hashes (edited):

data = [
    {id: 1, name: "Amy", win: 1, defeat: 0},
    {id: 1, name: "Amy", win: 1, defeat: 3},
    {id: 2, name: "Carl", win: 0, defeat: 1},
    {id: 2, name: "Carl", win: 2, defeat: 1}
]

How can I group or merge into something like this using the key "name" as reference:

data = [
    {id: 1, name: "Amy", win: 2, defeat: 3},
    {id: 2, name: "Carl", win: 2, defeat: 2}
]

edited I forgot to mention that I have an ID too that can't be added.

3
  • Does this (stackoverflow.com/questions/5191578/…) help you? Commented Jun 30, 2014 at 18:42
  • The rule for merger is not clear. What gives id: 1 for name: "Amy" while giving it win: 2? Commented Jun 30, 2014 at 19:09
  • @sawa The merger rule is crystal clear: sum wins and defeats grouped by name. Commented Jun 30, 2014 at 19:12

3 Answers 3

7

Here is my try

#!/usr/bin/env ruby

data = [
    {"name"=> "Amy", "win" => 1, "defeat" => 0},
    {"name"=> "Amy", "win" => 1, "defeat" => 3},
    {"name"=> "Carl", "win" => 0, "defeat" => 1},
    {"name"=> "Carl", "win" => 2, "defeat" => 1}
]

merged_hash = data.group_by { |h| h['name'] }.map do |_,val| 
  val.inject do |h1,h2| 
    h1.merge(h2) do |k,o,n|
      k == 'name' ? o : o + n 
    end
  end
end

merged_hash
# => [{"name"=>"Amy", "win"=>2, "defeat"=>3},
#     {"name"=>"Carl", "win"=>2, "defeat"=>2}]

Answer to the edited post :-

#!/usr/bin/env ruby

data = [
    {id: 1, name: "Amy", win: 1, defeat: 0},
    {id: 1, name: "Amy", win: 1, defeat: 3},
    {id: 2, name: "Carl", win: 0, defeat: 1},
    {id: 2, name: "Carl", win: 2, defeat: 1}
]

merged_hash = data.group_by { |h| h.values_at(:name, :id) }.map do |_,val| 
  val.inject do |h1,h2| 
    h1.merge(h2) do |k,o,n|
      %i(id name).include?(k) ? o : o + n 
    end
  end
end

merged_hash
# => [{:id=>1, :name=>"Amy", :win=>2, :defeat=>3},
#     {:id=>2, :name=>"Carl", :win=>2, :defeat=>2}]
Sign up to request clarification or add additional context in comments.

2 Comments

Each of his data lines are JSON. You can do a JSON.parse before each line like this data = []; data << JSON.parse(' {"name": "Amy", "win": 1, "defeat": 0} '); data << JSON.parse(' {"name": "Amy", "win": 1, "defeat": 3} '); data << JSON.parse(' {"name": "Carl", "win": 0, "defeat": 1} '); data << JSON.parse(' {"name": "Carl", "win": 2, "defeat": 1} '); . You can use this before following this answer.
@ArupRakshit, your solution is OK, but I forgot to mention that I have an Id too, and I wrote the hash correctly this time.
1

You can do it in one pass with each_with_object and a Hash-memo with an appropriate default. For example:

data.each_with_object(Hash.new { |h, k| h[k] = { :id => k.first, :name => k.last, :win => 0, :defeat => 0 } }) do |h, m|
  k = h.values_at(:id, :name)
  m[k][:win   ] += h[:win   ]
  m[k][:defeat] += h[:defeat]
end.values

The basic trick is to cache the results indexed by an appropriate key ([ h[:id], h[:name] ] in this case) and use the values to store what you're after. The default proc on the m Hash autovivifies cached values and then you can apply simple summations during iteration. And a final values call to unwrap the cache.

Comments

0

Good place where you can use group_by

result = []
data.group_by{|d| d[:id]}.each do {|name, records|
  win = 0
  defeat = 0

  records.each do |r|
    win += r[:win]
    defeat += r[:defeat]
  end
  f = records.first
  results << {:id => f[:id], :name => f[:name], :win => win, :defeat => defeat}
end

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.