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