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
v
is not a hash leave it as is (setn[k] = v
) - If the
v
is a hash andk
isperiods
- sort the value (n[k] = <your sorting logic>
) - Otherwise (
v
is a hash butk
is 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...