ruby - Merge two arrays of hashes based on comparison of multiple keys -
i have 2 arrays of hashes:
a1 = [{ id: 12496, name: "robert", email: "robert@hotmail.com" }, ...] a2 = [{ id: 12496, name: "robert", order_no: 5511426 }, ...]
i find hashes in a2
id
, name
fields match id
, name
fields of entry in a1
(without caring email
or other items make way a2
), , merge value of order_no
a1
hash @ values. i.e. end with:
[{ id: 12496, name: "robert", email: "robert@example.com", order_no: 5511426 } ...]
also want ignore elements present in a2 not in a1.
i'm doing following:
a1.each |a1_hash| matching_hash = a2.find { |a2_hash| data_matches?(a1_hash, a2_hash) } if a2.present? a1_hash["order_no"] = a2_hash["order_no"] if matching_hash.present? a2.delete(a2_hash) end
but there faster way?
this can done quite cleanly using few of ruby's built-in methods.
a1 = [{ id: 12496, name: "robert", email: "robert@hotmail.com" }, { id: 12497, name: "lola", email: "lola@hotmail.com" }, { id: 12498, name: "hank", email: "hank@hotmail.com" }] a2 = [{ id: 12497, name: "lola", order_no: 5511427 }, { id: 12496, name: "robert", order_no: 5511426 }] index = a2.group_by{|entry| [entry[:id], entry[:name]] } a1.map{|entry| (index[[entry[:id], entry[:name]]] || []).reduce(entry, :merge) }
result:
[{:id=>12496, :name=>"robert", :email=>"robert@hotmail.com", :order_no=>5511426}, {:id=>12497, :name=>"lola", :email=>"lola@hotmail.com", :order_no=>5511427}, {:id=>12498, :name=>"hank", :email=>"hank@hotmail.com"}]
breakdown:
first, use group_by
build table of entries in a2 potentially merged entries in a1. index table on id , name keys, since factors we're using determine entries match:
index = a2.group_by{|entry| [entry[:id], entry[:name]] }
this produces result:
{[12497, "lola"]=>[{:id=>12497, :name=>"lola", :order_no=>5511427}], [12496, "robert"]=>[{:id=>12496, :name=>"robert", :order_no=>5511426}]}
next, map
each entry in a1
new form, order numbers in index merged:
a1.map{|entry| # ... }
to value we're mapping each entry to, start getting array containing values in a2
suitable merge entry a1
:
(index[[entry[:id], entry[:name]]] || [])
this return [{:id=>12497, :name=>"lola", :order_no=>5511427}]
lola, , empty array hank, has no matching entry in a2
.
then, starting entry a1
, reduce
entries index 1 hash using merge
(e.g. reduce(entry, :merge)
), results in entry {:id=>12496, :name=>"robert", :email=>"robert@hotmail.com", :order_no=>5511426}
.
all might seem bit complicated if you're unfamiliar methods in ruby's core library. once understand simple functional programing concepts map , reduce, it's not difficult come simple , powerful solutions this.
Comments
Post a Comment