Dave Long
Dave Long

Reputation: 9759

Grouping hashes together to concat a key's values

I have an array of hashes like the following:

records = [
    { id: "PU525", note: "Foo" },
    { id: "PU525", note: "Bar" },
    { id: "DT525", note: "Hello World" },
    { id: "PU680", note: "Fubar" }
]

The end result should be:

result = [
    { id: "PU525", note: "FooBar" },
    { id: "DT525", note: "Hello World" },
    { id: "PU680", note: "Fubar" }
]

I started playing a little bit with Enumerable#group_by with the following:

results = records.group_by { |record| record[:id] }

# Results in:
# {
#     "PU525": [
#         { id: "PU525", note: "Foo" },
#         { id: "PU525", note: "Bar" }
#     ],
#     "DT525": { id: "DT525", note: "Hello World" },
#     "PU680": { id: "PU680", note: "Fubar" }
# }

The next step would be to use inject to reduce the data down further, but I'm wondering if there's an easier way to reduce down the original array to what I'm looking for as a result without so many steps?

Upvotes: 0

Views: 57

Answers (3)

Cary Swoveland
Cary Swoveland

Reputation: 110725

Here are a couple of variants of the above.

Using Enumerable#group_by

records.group_by { |h| h[:id] }.map { |id, arr|
  { id: id, note: arr.map { |h| h[:note] }.join } }
  #=> [{:id=>"PU525", :note=>"FooBar"},
  #    {:id=>"DT525", :note=>"Hello World"},
  #    {:id=>"PU680", :note=>"Fubar"}]

Using Hash#update (aka merge!)

records.each_with_object({}) { |g,h| h.update(g[:id]=>g) { |_,og,ng|
  { id: g[:id], note: og[:note]+ng[:note] } } }.values
  #=>[{:id=>"PU525", :note=>"FooBar"},
  #   {:id=>"DT525", :note=>"Hello World"},
  #   {:id=>"PU680", :note=>"Fubar"}]

Upvotes: 0

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121000

You are nearly done:

records = [ 
    { id: "PU525", note: "Foo" },
    { id: "PU525", note: "Bar" },
    { id: "DT525", note: "Hello World" },
    { id: "PU680", note: "Fubar" }
]

arr = records.group_by { |record| record[:id] }
             .map do |k, v|
                [k, v.reduce('') { |memo, h| memo + h[:note] } ] 
             end
             .map do |a| 
                { id: a.first, note: a.last }
             end
#⇒ [
#     { id: "PU525", note: "FooBar" },
#     { id: "DT525", note: "Hello World" },
#     { id: "PU680", note: "Fubar" }
# ]

UPD After all, you have me entagled with this silly group_by approach. Everything is just easier.

records.inject({}) do |memo, el| 
  (memo[el[:id]] ||= '') << el[:note]
  memo
end.map { |k, v| { id: k, note: v } }

Upvotes: 3

Arup Rakshit
Arup Rakshit

Reputation: 118289

Here is another way, after using #group_by :

records = [
    { id: "PU525", note: "Foo" },
    { id: "PU525", note: "Bar" },
    { id: "DT525", note: "Hello World" },
    { id: "PU680", note: "Fubar" }
]

hsh = records.group_by { |record| record[:id] }.map do |_, v|
  v.inject do |h1, h2|
    h1.update(h2) { |k, o, n| k == :note ? o << n : n }
  end
end

p hsh
# >> [{:id=>"PU525", :note=>"FooBar"}, {:id=>"DT525", :note=>"Hello World"}, {:id=>"PU680", :note=>"Fubar"}]

Upvotes: 1

Related Questions