Charts with empty months

2019-06-12 07:31发布

问题:

I have a hash

{"Apr 2016"=>6.0, "Aug 2016"=>7.5, "Jan 2017"=>8.666666666666666, "Apr 2017"=>7.333333333333333, "May 2017"=>7.571428571428571, "Jun 2017"=>6.75, "Jul 2017"=>6.7272727272727275}

I want display a chart line but the empty months in my hash create ugly chart, I would know how get empty months and give previous value has value to get something like

{"Apr 2016"=>6.0, "May 2016"=>6, "June 2016"=>6, "July 2016"=>6 "Aug 2016"=>7.5, "Jan 2017"=>8.666666666666666...}

UPDATE: I get all values but i dont know how atribute the previous value when the value is empty, i tried many things but nothing work

回答1:

Code

require 'date'

def fill_in(h)
  month, date_last = h.keys.map { |s| Date.strptime(s, '%b %Y') }.minmax
  h_out = {}
  last = nil
  loop do
    str = month.strftime('%b %Y')
    h_out[str] = h.fetch(str, last)
    last = h_out[str]
    return h_out if month == date_last
    month = month >> 1
  end
end

Example

h = { "May 2016"=>6.0, "Aug 2016"=>7.5, "Jan 2017"=>8.6, "Nov 2016"=>7.3 }
fill_in(h)
  #=> {"May 2016"=>6.0, "Jun 2016"=>6.0, "Jul 2016"=>6.0,
  #    "Aug 2016"=>7.5, "Sep 2016"=>7.5, "Oct 2016"=>7.5,
  #    "Nov 2016"=>7.3, "Dec 2016"=>7.3, "Jan 2017"=>8.6}

Explanation

See Date::strptime, Date#strftime, Date#>>, Enumerable#minmax and Hash#fetch.

Let's go though the steps for h given in the example.

month, date_last = h.keys.map { |s| Date.strptime(s, '%b %Y') }.minmax
  #=> [#<Date: 2016-05-01 ((2457510j,0s,0n),+0s,2299161j)>,
  #    #<Date: 2017-01-01 ((2457755j,0s,0n),+0s,2299161j)>]
month
  #=> #<Date: 2016-05-01 ((2457510j,0s,0n),+0s,2299161j)>
date_last
  #=> #<Date: 2017-01-01 ((2457755j,0s,0n),+0s,2299161j)>
h_out = {}
last = nil

Perform the loop calculation once

str = month.strftime('%b %Y')
  #=> #<Date: 2016-05-01 ((2457510j,0s,0n),+0s,2299161j)>.strftime('%b %Y')
  #=> "May 2016"
h_out[str] = h.fetch("May 2016", nil)
  #=> h.fetch(str, last)
  #=> 6.0
last = h_out[str]
  #=> 6.0
return h_out if month == date_last
  # <do not return>
month = month >> 1
  #=> #<Date: 2016-06-01 ((2457541j,0s,0n),+0s,2299161j)>

Now proceed through the loop once more.

str = month.strftime('%b %Y')
  #=> "Jun 2016"
h_out[str] = h.fetch(str, last)
  #=> 6.0

This time fetch uses its default (last #=> 6.0) because h has no key "Jun 2016".

last = h_out[str]
  #=> 6.0
return h_out if month == date_last
  # <do not return>
month = month >> 1
  #=> #<Date: 2016-07-01 ((2457571j,0s,0n),+0s,2299161j)>

The remaining calculations are similar.



回答2:

Please read the Update as well.

You will need to fill the gaps:

require 'date'
data = {'Apr 2016' => 6.0, 'Aug 2016' => 7.5, 'Jan 2017' => 8.666666666666666, 'Apr 2017' => 7.333333333333333, 'May 2017' => 7.571428571428571, 'Jun 2017' => 6.75, 'Jul 2017' => 6.7272727272727275}
range = Date.parse('1.4.2016')..Date.parse('1.7.2017')

range.each do |date|
  key = date.strftime('%b %Y')
  data[key] ||= 0
end

This creates a date range with start and end date and then iterates it. It creates a String from the date (strftime) and checks if it is already in the hash (||=) and if not, assigns it with value set to 0 Depending on how you render the chart and what you actually want to display, 0 might be the wrong value, perhaps you'll need to set it to nil.

UPDATE: I just realized that this is not the most efficient way to do this because the range will contain a entry per day, not per month. I'll leave the code in the answer but you should use something like:

require 'date'
data = {'Apr 2016' => 6.0, 'Aug 2016' => 7.5, 'Jan 2017' => 8.666666666666666, 'Apr 2017' => 7.333333333333333, 'May 2017' => 7.571428571428571, 'Jun 2017' => 6.75, 'Jul 2017' => 6.7272727272727275}

current = Date.parse('1.4.2016')
stop = Date.parse('1.7.2017')

while current <= stop do
  key = current.strftime('%b %Y')
  data[key] ||= 0
  current = current >> 1
end