Jasjeet Singh
Jasjeet Singh

Reputation: 332

Rails: How to merge two hashes if a specific key has the same value?

I'm trying to merge hashes if a specific key has the same value.

here is the array

[{

  id: 77,
  member_phone: "9876543210",
  created_at: "2017-05-03T11:06:03.000Z",
  name: "Sure"
},
{
  id: 77,
  member_phone: "123456789",
  created_at: "2017-05-03T11:06:03.000Z",
  name: "Sure"
},
{
  id: 78,
  member_phone: "12345",
  created_at: "2017-05-03T11:06:03.000Z",
  name: "XYZ"
}]

and the required output:

[{
  id: 77,
  member_phone: "123456789,9876543210",
  created_at: "2017-05-03T11:06:03.000Z",
  name: "Sure"
},
{
  id: 78,
  member_phone: "12345",
  created_at: "2017-05-03T11:06:03.000Z",
  name: "XYZ"
}]

here's the code I tried:

merge_users.group_by { |h1| h1["id"] }.map do |k,v|
  { "id" => k, :member_phone =>  v.map { |h2| h2[:member_phone] }.join(", ") }
end

how can I do it?

Upvotes: 1

Views: 2584

Answers (3)

Cary Swoveland
Cary Swoveland

Reputation: 110645

arr = [
  { id: 77, phone: "9876543210", name: "Sure" },
  { id: 77, phone: "123456789", name: "Sure" },
  { id: 78, phone: "12345", name: "XYZ" }
]

You could use the form of Hash#update (aka merge!) that uses a block to compute the values of keys that are present in both hashes being merged.

arr.each_with_object({}) { |g,h| h.update(g[:id]=>g) { |_,o,n|
  o.merge(phone: "#{o[:phone]}#{n[:phone]}") } }.values
  #=> [{:id=>77, :phone=>"9876543210123456789", :name=>"Sure"},
  #    {:id=>78, :phone=>"12345", :name=>"XYZ"}]

Note that the receiver of Hash#values is the following.

#=> {77=>{:id=>77, :phone=>"9876543210123456789", :name=>"Sure"},
#    78=>{:id=>78, :phone=>"12345", :name=>"XYZ"}}

See the doc for Hash#update for definitions of the three block variables _, o and n. I used an underscore for the first variable (a valid name for a local variable) to signify that it is not used in the block calculation (a common practice).

Note that Hash#update can almost always be used when Enumerable#group_by can be used, and vice-versa.

Here's one way to use Hash#group_by here.

arr.group_by { |h| h[:id] }.
    map { |_,a| a.first.merge(phone: a.map { |h| h[:phone] }.join) }
  #=> [{:id=>77, :phone=>"9876543210123456789", :name=>"Sure"},
  #    {:id=>78, :phone=>"12345", :name=>"XYZ"}]         

Note that

arr.group_by { |h| h[:id] }
  #=> {77=>[{:id=>77, :phone=>"9876543210", :name=>"Sure"},
  #         {:id=>77, :phone=>"123456789", :name=>"Sure"}],
  #    78=>[{:id=>78, :phone=>"12345", :name=>"XYZ"}]}

Upvotes: 0

Rajagopalan
Rajagopalan

Reputation: 6064

The following code would work for your given example.

code

result = arr.group_by {|h| h[:id]}.values.map do |arr|
  arr.reduce do |h1, h2|
    h1.merge(h2) do |k, ov, nv|
      ov.eql?(nv) ? ov : [ov, nv].join(",")
    end
  end
end

p result

#=>[{:id=>77, :member_phone=>"9876543210,123456789", :created_at=>"2017-05-03T11:06:03.000Z", :name=>"Sure"}, {:id=>78, :member_phone=>"12345", :created_at=>"2017-05-03T11:06:03.000Z", :name=>"XYZ"}]

Upvotes: 1

Pascal
Pascal

Reputation: 8637

How about:

grouped = data.group_by do |item|
  item[:id]
end

combined = grouped.map do |_id, hashes|
  hashes.inject({}) do |memo, hash|
    memo.merge(hash)
  end
end

It works in two passes:

First group all hashes by the value of the :id key This returns a Hash with the id as key, and an array (of all the hashes with this id) as value.

In a second pass all the hashes are merged and mapped to an array again.

Upvotes: 0

Related Questions