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
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.
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