Multiple hashes from one hash
I have a hash:
hash = {"a_1_a" => "1", "a_1_b" => "2", "a_1_c" => "3", "a_2_a" => "3", "a_2_b" => "4", "a_2_c" => "4"}
What is the best way to get the following sub-hashes:
[{"a_1_a" => "1", "a_1_b" => "2", "a_1_c" => "3"}, {"a_2_a" => "3", "a_2_b" => "4", "a_2_c" => "4"}]
I want them to be grouped by key, based on regexp /^a_(\d+)/
. I will have 50+ key / value pairs in the original hash, so something dynamic will work best if anyone has suggestions.
If you care only about the middle component, you can use group_by
to get most of the way from you:
hash.group_by do |k,v| k.split('_')[1] end.values.map do |list| Hash[list] end # => [{"a_1_a"=>"1", "a_1_b"=>"2", "a_1_c"=>"3"}, {"a_2_a"=>"3", "a_2_b"=>"4", "a_2_c"=>"4"}]
The final step is to extract the grouped lists and merge them back into the required hashes.
the code
def partition_hash(hash) hash.each_with_object({}) do |(k,v), h| key = k[/(?<=_).+(?=_)/] h[key] = (h[key] || {}).merge(k=>v) end.values end
Example
hash = {"a_1_a"=>"1", "a_1_b"=>"2", "a_1_c"=>"3", "a_2_a"=>"3", "a_2_b"=>"4", "a_2_c"=>"4"} partition_hash(hash) #=> [{"a_1_a"=>"1", "a_1_b"=>"2", "a_1_c"=>"3"}, # {"a_2_a"=>"3", "a_2_b"=>"4", "a_2_c"=>"4"}]
Explanation
Following are the steps.
enum = hash.each_with_object({}) #=> #<Enumerator: {"a_1_a"=>"1", "a_1_b"=>"2", "a_1_c"=>"3", "a_2_a"=>"3", # "a_2_b"=>"4", "a_2_c"=>"4"}:each_with_object({})>
The first element of this counter is generated and passed to the block, and block variables are calculated using parallel assignment.
(k,v), h = enum.next #=> [["a_1_a", "1"], {}] k #=> "a_1_a" v #=> "1" h #=> {}
and block calculation is performed.
key = k[/(?<=_).+(?=_)/] #=> "1" h[key] = (h[key] || {}).merge(k=>v) #=> h["1"] = (h["1"] || {}).merge("a_1_a"=>"1") #=> h["1"] = (nil || {}).merge("a_1_a"=>"1") #=> h["1"] = {}.merge("a_1_a"=>"1") #=> h["1"] = {"a_1_a"=>"1"}
so now
h #=> {"1"=>{"a_1_a"=>"1"}}
The next enum
value is now generated and passed to the block, and the following calculations are performed.
(k,v), h = enum.next #=> [["a_1_b", "2"], {"1"=>{"a_1_a"=>"1"}}] k #=> "a_1_b" v #=> "2" h #=> {"1"=>{"a_1_a"=>"1"}} key = k[/(?<=_).+(?=_)/] #=> "1" h[key] = (h[key] || {}).merge(k=>v) #=> h["1"] = (h["1"] || {}).merge("a_1_b"=>"2") #=> h["1"] = ({"a_1_a"=>"1"}} || {}).merge("a_1_b"=>"2") #=> h["1"] = {"a_1_a"=>"1"}}.merge("a_1_b"=>"2") #=> h["1"] = {"a_1_a"=>"1", "a_1_b"=>"2"}
After the remaining four enum
elements have been passed to the block, the following is returned.
h #=> {"1"=>{"a_1_a"=>"1", "a_1_b"=>"2", "a_1_c"=>"3"}, # "2"=>{"a_2_a"=>"3", "a_2_b"=>"4", "a_2_c"=>"4"}}
The final step is to simply extract the values.
h.values #=> [{"a_1_a"=>"1", "a_1_b"=>"2", "a_1_c"=>"3"}, # {"a_2_a"=>"3", "a_2_b"=>"4", "a_2_c"=>"4"}]