简体   繁体   中英

Ruby Array of hashes: Better way to update deeply hash

I have an array consisting of hashes in the following form:

array = [
          {"id": 1, "items": [{"item_code": 1, "qty": 2},{"item_code": 2, "qty": 2}]},
          {"id": 2, "items": [{"item_code": 3, "qty": 2},{"item_code": 4, "qty": 2}]},
          {"id": 1, "items": [{"item_code": 5, "qty": 2},{"item_code": 6, "qty": 2}]},
          {"id": 2, "items": [{"item_code": 7, "qty": 2},{"item_code": 8, "qty": 2}]}
        ]

Do we have any convenient way to merge items according to ids:

Expected output:

array = [
          {"id": 1, "items": [
                               {"item_code": 1, "qty": 2},
                               {"item_code": 2, "qty": 2},
                               {"item_code": 5, "qty": 2},
                               {"item_code": 6, "qty": 2}
                             ]
          },
          {"id": 2, "items": [
                               {"item_code": 3, "qty": 2},
                               {"item_code": 4, "qty": 2},
                               {"item_code": 7, "qty": 2},
                               {"item_code": 8, "qty": 2}
                             ]
          }
       ]

I tried with group_by, which doesn't satisfy the output format.

Use group_by and each_with_object :

ary.group_by { |elem| elem[:id] }.
    each_with_object([]) do |(id, grouped_ary), out|
                           out << { id: id, items: grouped_ary.map { |h| h[:items] }.reduce(:|) }
                         end
 => [{:id=>1, :items=>[{:item_code=>1, :qty=>2},
                       {:item_code=>2, :qty=>2},
                       {:item_code=>5, :qty=>2},
                       {:item_code=>6, :qty=>2}]},
     {:id=>2, :items=>[{:item_code=>3, :qty=>2},
                       {:item_code=>4, :qty=>2}, 
                       {:item_code=>7, :qty=>2},
                       {:item_code=>8, :qty=>2}]}] 

There is no need for group_by . Go straight with each_with_object directly from the scratch.

array.
  each_with_object({}) do |hash, acc|
    acc[hash[:id]] ?
      acc[hash[:id]][:items] |= hash[:items] : 
      acc[hash[:id]] = hash
  end.values
#⇒ [{:id=>1, :items=>[{:item_code=>1, :qty=>2},
#                     {:item_code=>2, :qty=>2},
#                     {:item_code=>5, :qty=>2},
#                     {:item_code=>6, :qty=>2}]},
#   {:id=>2, :items=>[{:item_code=>3, :qty=>2},
#                     {:item_code=>4, :qty=>2},
#                     {:item_code=>7, :qty=>2},
#                     {:item_code=>8, :qty=>2}]}]

You can combine group_by and transform_values! (for the latter you need Ruby 2.4.0 and later, otherwise you can use each_with_object as @Jagdeep pointed out)

array.group_by { |item| item[:id] }
    .transform_values! { |v| v.flat_map { |subitem| subitem[:items] } }
    .map { |(id, items)| Hash["id", id, "items", items] }

首先,我映射ID,然后为每个ID用两个键进行哈希处理:ID和每个条目项的展平图:

result = array.group_by { |e| e[:id] }.map { |id, entries| {id: id, items: entries.flat_map { |entry| entry[:items] }} }
array.each_with_object({}) { |g,h| h.update(g[:id]=>g[:items]) { |_,o,n| o+n } }.
      map { |k,v| { id: k, items: v } }
  #=> [{:id=>1, :items=>[{:item_code=>1, :qty=>2}, {:item_code=>2, :qty=>2},
  #                      {:item_code=>5, :qty=>2}, {:item_code=>6, :qty=>2}]},
  #    {:id=>2, :items=>[{:item_code=>3, :qty=>2}, {:item_code=>4, :qty=>2},
  #                      {:item_code=>7, :qty=>2}, {:item_code=>8, :qty=>2}]}]

The first step is:

array.each_with_object({}) { |g,h| h.update(g[:id]=>g[:items]) { |_,o,n| o+n } }
  #=> {1=>[{:item_code=>1, :qty=>2}, {:item_code=>2, :qty=>2},
  #        {:item_code=>5, :qty=>2}, {:item_code=>6, :qty=>2}],
  #    2=>[{:item_code=>3, :qty=>2}, {:item_code=>4, :qty=>2},
  #        {:item_code=>7, :qty=>2}, {:item_code=>8, :qty=>2}]}

This makes use of the form of Hash#update (aka merge! ) that employs a block to determine the values of keys that are present in both hashes being merged. See the doc for the definitions of the three block variables.

Alternatively, one could write the following.

array.each_with_object({}) { |g,h| h.update(g[:id]=>g) { |_,o,n|
  o.merge(n) { |k,oo,nn| k == :items ? oo+nn : oo } } }.values

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM