Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
171 views
in Technique[技术] by (71.8m points)

ruby on rails - Merge Hashes within Array of Hashes

I'm generating an array of hashes like this:

hash = Modality.joins(:exam).where(exams: {physician_id: physician}).map {|m|
  {name: m.name, data: {m.exam.from_date => m.total}}
}

The output:

[
  {:name=>"MR", :data=>{Sun, 01 Jan 2017=>44}}, 
  {:name=>"CT", :data=>{Sun, 01 Jan 2017=>7}}, 
  {:name=>"US", :data=>{Sun, 01 Jan 2017=>1}}, 
  {:name=>"MR", :data=>{Wed, 01 Feb 2017=>41}}, 
  {:name=>"CT", :data=>{Wed, 01 Feb 2017=>4}}, 
  {:name=>"MR", :data=>{Wed, 01 Mar 2017=>66}}, 
  {:name=>"CT", :data=>{Wed, 01 Mar 2017=>6}}, 
  {:name=>"XR", :data=>{Wed, 01 Mar 2017=>1}}, 
  {:name=>"CT", :data=>{Sat, 01 Apr 2017=>8}}, 
  {:name=>"US", :data=>{Sat, 01 Apr 2017=>1}}, 
  {:name=>"MR", :data=>{Sat, 01 Apr 2017=>73}}
]

I need to group and merge the data part of the hash based on the name key like this:

[
  {:name=>"MR", :data=>{Sun, 01 Jan 2017=>44, Wed, 01 Feb 2017=>41, Wed, 01 Mar 2017=>66, Sat, 01 Apr 2017=>73}}
  {:name=>"CT", :data=>{Sun, 01 Jan 2017=>7, Wed, 01 Feb 2017=>4, Wed, 01 Mar 2017=>6, Sat, 01 Apr 2017=>8}}
  {:name=>"US", :data=>{Sun, 01 Jan 2017=>1, Sat, 01 Apr 2017=>1, Sun}}
  {:name=>"XR", :data=>{Wed, 01 Mar 2017=>1}}
]

I tried reducing and merging like this:

exams = hash.group_by {|d| d[:name]}.map {|k, v| v.reduce(:merge)}

But it's not the desired output:

[
  {:name=>"MR", :data=>{Sun, 01 Jan 2017=>44}}, 
  {:name=>"CT", :data=>{Sun, 01 Jan 2017=>7}}, 
  {:name=>"US", :data=>{Sun, 01 Jan 2017=>1}}, 
  {:name=>"XR", :data=>{Wed, 01 Mar 2017=>1}}
]

How would I correctly merge this hash?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Here is your array a, which I've reduced in size and put the dates in quotes.

arr = [
  {:name=>"MR", :data=>{"Sun, 01 Jan 2017"=>44}}, 
  {:name=>"CT", :data=>{"Sun, 01 Jan 2017"=>7}}, 
  {:name=>"US", :data=>{"Sun, 01 Jan 2017"=>1}}, 
  {:name=>"MR", :data=>{"Wed, 01 Feb 2017"=>41}}, 
  {:name=>"CT", :data=>{"Wed, 01 Feb 2017"=>4}}, 
  {:name=>"CT", :data=>{"Wed, 01 Mar 2017"=>6}}, 
  {:name=>"XR", :data=>{"Wed, 01 Mar 2017"=>1}},
  {:name=>"US", :data=>{"Sat, 01 Apr 2017=>1}}
]

A simple modification is required to convert the date strings to Date objects1, but I will not do that as the methods below do not depend on whether the dates are strings or Date objects.

Here are two ways to obtain the desired return value.

Use Enumerable#group_by

arr.group_by { |h| h[:name] }.
    map { |k,v| { name: k, data: v.reduce({}) { |h,g| h.merge(g[:data]) } } }
  #=> [{:name=>"MR", :data=>{"Sun, 01 Jan 2017"=>44, "Wed, 01 Feb 2017"=>41}},
  #    {:name=>"CT", :data=>{"Sun, 01 Jan 2017"=>7, "Wed, 01 Feb 2017"=>4,
  #                          "Wed, 01 Mar 2017"=>6}},
  #    {:name=>"US", :data=>{"Sun, 01 Jan 2017"=>1, "Sat, 01 Apr 2017"=>1}},
  #    {:name=>"XR", :data=>{"Wed, 01 Mar 2017"=>1}}]

Note that Enumerable#group_by produces the following hash.

arr.group_by { |h| h[:name] }
  #=> {"MR"=>[{:name=>"MR", :data=>{"Sun, 01 Jan 2017"=>44}},
  #           {:name=>"MR", :data=>{"Wed, 01 Feb 2017"=>41}}],
  #    "CT"=>[{:name=>"CT", :data=>{"Sun, 01 Jan 2017"=>7}},
  #           {:name=>"CT", :data=>{"Wed, 01 Feb 2017"=>4}},
  #           {:name=>"CT", :data=>{"Wed, 01 Mar 2017"=>6}}],
  #    "US"=>[{:name=>"US", :data=>{"Sun, 01 Jan 2017"=>1}},
  #           {:name=>"US", :data=>{"Sun, 01 Jan 2017"=>1}}],
  #    "XR"=>[{:name=>"XR", :data=>{"Wed, 01 Mar 2017"=>1}}]}

One could alternatively replace

v.reduce({}) { |h,g| h.merge(g[:data]) }

with

v.map { |h| h[:data] }.reduce(&:merge)

Use Hash#update (aka merge!) and Hash#merge

arr.each_with_object({}) do |g,h|
  h.update(g[:name]=>g) { |_,n,o| { name: n[:name], data: n[:data].merge(o[:data]) } }
end.values
  #=> [{:name=>"MR", :data=>{"Sun, 01 Jan 2017"=>44, "Wed, 01 Feb 2017"=>41}},
  #    {:name=>"CT", :data=>{"Sun, 01 Jan 2017"=>7, "Wed, 01 Feb 2017"=>4,
  #                          "Wed, 01 Mar 2017"=>6}},
  #    {:name=>"US", :data=>{"Sun, 01 Jan 2017"=>1, "Sat, 01 Apr 2017"=>1}},
  #    {:name=>"XR", :data=>{"Wed, 01 Mar 2017"=>1}}]

This uses the form of Hash#update that employs a block for determining the values of keys that are present in both hashes being merged. See the method's doc for details, particularly the definitions of the three block variables, _, o and n. (The first block variable, which holds the common key, is here represented by an underscore [a valid local variable] to signify that it is not used in the block calculation.)

1 require 'date'; arr = a.map do |h|; key, value = h[:data].flatten; h.merge(data: { Date.parse(key)=>value }); end


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...