Search code examples
ruby-on-railsrubyapexcharts

Fill an array of hash with missing values


I'm trying to build some Heatmap specifics charts for ApexChart. So far, I got this Array of Hash, for 3 activities.

[{
  :name => "Activity 1",
  :data => {
    "May 2020" => 37, "June 2020" => 17, "July 2020" => 9, "August 2020" => 18
  }
}, {
  :name => "Activity 2",
  :data => {
    "May 2020" => 3
  }
}, {
  :name => "Activity 3",
  :data => {
    "July 2020" => 5, "November 2020" => 11
  }
}]

On Activity 3, we got only 2 months which are, July and November.

My needs would be to fill for each Hash, all missing date, and filling them with 0 as value. My awaited results would be

[{
  :name => "Activity 1",
  :data => {
    "May 2020" => 37, "June 2020" => 17, "July 2020" => 9, "August 2020" => 18, "November 2020" => 0
  }
}, {
  :name => "Activity 2",
  :data => {
    "May 2020" => 3, "June 2020" => 0, "July 2020" => 0, "August 2020" => 0, "November 2020" => 0
  }
}, {
  :name => "Activity 3",
  :data => {
    "May 2020" => , "June 2020" => 0, "July 2020" => 5, "August 2020" => 0, "November 2020" => 11
  }
}]

Yes, September is missing on purpose. I suppose the best way to achieve this would be to retrieve every months, one by one; Then to fill each arrays with the missing months; But I don't know how to achieve this.


Solution

  • If arr is your array of hashes, you could construct the desired array in two steps.

    require 'date'
    
    date_fmt = "%B %Y"
    first_month, last_month = arr.flat_map do |g|
      g[:data].keys
    end.map { |s| Date.strptime(s, date_fmt) }.minmax
      #=> [#<Date: 2020-05-01 ((2458971j,0s,0n),+0s,2299161j)>,
      #    #<Date: 2020-11-01 ((2459155j,0s,0n),+0s,2299161j)>] 
    
    h = (first_month..last_month).map do |d|
      d.strftime(date_fmt)
    end.product([0]).to_h
      #=> {"May 2020"=>0, "June 2020"=>0, "July 2020"=>0, "August 2020"=>0,
      #    "September 2020"=>0, "October 2020"=>0, "November 2020"=>0} 
    
    arr.map { |g| g.merge(:data => h.merge(g[:data])) }
      #=> [
      #     {
      #       :name=>"Activity 1",
      #       :data=>{
      #         "May 2020"=>37, "June 2020"=>17, "July 2020"=>9,
      #         "August 2020"=>18, "September 2020"=>0,
      #         "October 2020"=>0, "November 2020"=>0
      #       }
      #     },
      #     {
      #       :name=>"Activity 2",
      #       :data=>{
      #         "May 2020"=>3, "June 2020"=>0, "July 2020"=>0,
      #         "August 2020"=>0, "September 2020"=>0,
      #         "October 2020"=>0, "November 2020"=>0
      #       }
      #     },
      #     {
      #       :name=>"Activity 3",
      #       :data=>{
      #         "May 2020"=>0, "June 2020"=>0, "July 2020"=>5,
      #         "August 2020"=>0, "September 2020"=>0,
      #         "October 2020"=>0, "November 2020"=>11
      #       }
      #     }
      #   ] 
    

    See Enumerable#flat_map, Date::strptime, Array#minmax, Date#strftime, Array#product and Hash#merge. See also DateTime#strptime for date formatting directives.

    Note that in the calculation of first_month and last_month,

    [#<Date: 2020-05-01 ((2458971j,0s,0n),+0s,2299161j)>,
     #<Date: 2020-11-01 ((2459155j,0s,0n),+0s,2299161j)>].
      map { |d| d.strftime(date_fmt) }
      #=> ["May 2020", "November 2020"]