Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

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

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

1 Answer

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


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...