I've a nested JSONB hash that I require to display in date order.
The hash is stored like so:
hash =
{"residential_la"=>
{"current"=>{"periods"=>{"1/2023"=>"0", "2/2023"=>"0", "3/2023"=>"0", "4/2023"=>"0", "5/2023"=>"0", "6/2023"=>"0", "7/2023"=>"0", "8/2023"=>"0", "9/2023"=>"0", "10/2022"=>"0", "11/2022"=>"0", "12/2022"=>"0"}},
"original"=>{"periods"=>{"1/2023"=>"901", "2/2023"=>"1315", "3/2023"=>"4377", "4/2023"=>"1815", "5/2023"=>"1835", "6/2023"=>"896", "7/2023"=>"1996", "8/2023"=>"4219", "9/2023"=>"3369", "10/2022"=>"3335", "11/2022"=>"4198", "12/2022"=>"3127"}},
"NominalCode"=>"500"},
"residential_private"=>
{"current"=>{"periods"=>{"1/2023"=>"0", "2/2023"=>"0", "3/2023"=>"0", "4/2023"=>"0", "5/2023"=>"0", "6/2023"=>"0", "7/2023"=>"0", "8/2023"=>"0", "9/2023"=>"0", "10/2022"=>"0", "11/2022"=>"0", "12/2022"=>"0"}},
"original"=>{"periods"=>{"1/2023"=>"4389", "2/2023"=>"1265", "3/2023"=>"4496", "4/2023"=>"980", "5/2023"=>"1617", "6/2023"=>"1396", "7/2023"=>"4839", "8/2023"=>"4248", "9/2023"=>"1770", "10/2022"=>"3513", "11/2022"=>"1294", "12/2022"=>"4240"}},
"NominalCode"=>"520"}}
As you can see, being JSONB it's storing it in length order. I need to sort this when calling to display in the correct way.
I know for sure I can sort the periods nested hash with the following:
{ periods: periods_hash[:periods].sort_by { |k, _v| Date.strptime(k, "%d/%Y") }.to_h }
This creates a new hash of periods and sorts it, correcty.
My problem is getting to this nested hash and returning the full hash with no changes but the order of those periods.
My current efforts produced this:
def order_data
data.each do |_k, v|
v.each do |sk, sv|
next if sk.include? "NominalCode"
sv.each do |x, _y|
{ periods: x[:periods].sort_by { |k, _v| Date.strptime(k, "%d/%Y") }.to_h }
end
end
end
end
The above isn't working and gives me no implicit conversion of Symbol into Integer, I'm assuiming as I'm .each'ing there's a problem with enummerable and the symbols of the hash.
What I'm expecting in return is:
{"residential_la"=>
{"current"=>{"periods"=>{"10/2022"=>"0", "11/2022"=>"0", "12/2022"=>"0", "1/2023"=>"0", "2/2023"=>"0", "3/2023"=>"0", "4/2023"=>"0", "5/2023"=>"0", "6/2023"=>"0", "7/2022"=>"0", "8/2022"=>"0", "9/2022"=>"0"}},
"original"=>{"periods"=>{"10/2022"=>"0", "11/2022"=>"0", "12/2022"=>"0", "1/2023"=>"0", "2/2023"=>"0", "3/2023"=>"0", "4/2023"=>"0", "5/2023"=>"0", "6/2023"=>"0", "7/2022"=>"0", "8/2022"=>"0", "9/2022"=>"0"}},
"NominalCode"=>"500"},
"residential_private"=>
{"current"=>{"periods"=>{"10/2022"=>"0", "11/2022"=>"0", "12/2022"=>"0", "1/2023"=>"0", "2/2023"=>"0", "3/2023"=>"0", "4/2023"=>"0", "5/2023"=>"0", "6/2023"=>"0", "7/2022"=>"0", "8/2022"=>"0", "9/2022"=>"0"}},
"original"=>{"periods"=>{"10/2022"=>"0", "11/2022"=>"0", "12/2022"=>"0", "1/2023"=>"0", "2/2023"=>"0", "3/2023"=>"0", "4/2023"=>"0", "5/2023"=>"0", "6/2023"=>"0", "7/2022"=>"0", "8/2022"=>"0", "9/2022"=>"0"}},
"NominalCode"=>"500"}
I'm unsure whether I should be mapping to create a new hash, or just iterating through with each to only modify the sub hash, ultimately there's a lack of understanding on my part, any help and guidance is appreciated.
Thanks.
CodePudding user response:
Given the following task definition:
- we need to sort any hash under key
periods... - regardless of its (
periods) depth - and keep everything else intact
one could go with the simple recursive algorithm (let's name it deep_sort_periods):
- Start with a new hash (n)
- Check the next key/value (k/v) of the existing hash
- If the
vis not a hash leave it as is (setn[k] = v) - If the
vis a hash andkisperiods- sort the value (n[k] = <your sorting logic>) - Otherwise (
vis a hash butkis smth. different, notperiods) - repeat 1-4 for the nested hash and assign the result ton[k](n[k] = deep_sort_periods(v))
The implementation can literally follow the description, kinda:
def deep_sort_periods(hash)
hash.each_with_object({}) do |(k, v), new_hash|
if !v.is_a?(Hash)
new_hash[k] = v
elsif k == "periods"
new_hash[k] = v.sort_by { |k, _v| Date.strptime(k, "%d/%Y") }.to_h
else
new_hash[k] = deep_sort_periods(v)
end
end
end
and then
pry(main)> deep_sort_periods(hash)
=> {"residential_la"=>
{"current"=>{"periods"=>{"7/2022"=>"0", "8/2022"=>"0", "9/2022"=>"0", "10/2022"=>"0", "11/2022"=>"0", "12/2022"=>"0", "1/2023"=>"0", "2/2023"=>"0", "3/2023"=>"0", "4/2023"=>"0", "5/2023"=>"0", "6/2023"=>"0"}},
"original"=>{"periods"=>{"7/2022"=>"0", "8/2022"=>"0", "9/2022"=>"0", "10/2022"=>"0", "11/2022"=>"0", "12/2022"=>"0", "1/2023"=>"0", "2/2023"=>"0", "3/2023"=>"0", "4/2023"=>"0", "5/2023"=>"0", "6/2023"=>"0"}},
"NominalCode"=>"500"},
"residential_private"=>
{"current"=>{"periods"=>{"7/2022"=>"0", "8/2022"=>"0", "9/2022"=>"0", "10/2022"=>"0", "11/2022"=>"0", "12/2022"=>"0", "1/2023"=>"0", "2/2023"=>"0", "3/2023"=>"0", "4/2023"=>"0", "5/2023"=>"0", "6/2023"=>"0"}},
"original"=>{"periods"=>{"7/2022"=>"0", "8/2022"=>"0", "9/2022"=>"0", "10/2022"=>"0", "11/2022"=>"0", "12/2022"=>"0", "1/2023"=>"0", "2/2023"=>"0", "3/2023"=>"0", "4/2023"=>"0", "5/2023"=>"0", "6/2023"=>"0"}},
"NominalCode"=>"500"}}
Disclaimer: if there are more restrictions for the task definition - for example, periods are not arbitrarily nested but rather sit at some fixed depth - the task most probably can be solved in a more concise and performant way with no recursion at all...
