rsmets
rsmets

Reputation: 919

Flatten Hash while Iterating over hash's array elements. Ruby

The input hash can have nests of any combo of Arrays and Hashes (AoA, AoH, HoH, and HoA). Flatting the hash elements to have the proper key and delimiter of _> is no problem.

However, I'm having trouble when an Array comes into the picture and I need to grab each element and stick it to the proper key while continuing to build the output. The final output should be a 1-D array of hashes with the only difference being the each array elements.

For example:

if the input hash is: {:x => 333, :y => 13, :z => [1,2,{:zz => [40,50]},[10,20]], :a => {:o => "1", :p => "2"}}

The final result should be:

`[{:x => 333, :y => 13, :z => 1, :z_>zz => 40, :a_>o => 1, a_>p => 2},  
 {:x => 333, :y => 13, :z => 1, :z_>zz => 50, :a_>o => 1, a_>p => 2},  
 {:x => 333, :y => 13, :z => 2, :z_>zz => 40, :a_>o => 1, a_>p => 2},  
 {:x => 333, :y => 13, :z => 2, :z_>zz => 50, :a_>o => 1, a_>p => 2},  
 {:x => 333, :y => 13, :z => 10, :z_>zz => 40, :a_>o => 1, a_>p => 2},  
 {:x => 333, :y => 13, :z => 10, :z_>zz => 50, :a_>o => 1, a_>p => 2},  
 {:x => 333, :y => 13, :z => 20, :z_>zz => 40, :a_>o => 1, a_>p => 2},  
 {:x => 333, :y => 13, :z => 20, :z_>zz => 50, :a_>o => 1, a_>p => 2}]`

Upvotes: 0

Views: 181

Answers (1)

hirolau
hirolau

Reputation: 13901

This is long and complicated, but at least it works:

my_hash = {:x => 333, :y => 13, :z => [1,2,{:zz => [40,50]},[10,20]], :a => {:o => "1", :p => "2"}}


# Create Recursive function to get values:
def advance_hash_flattener(input, parent=[])
  case input
    when Hash then input.flat_map{|key, val|
      advance_hash_flattener(val, parent+[key])}
    when Array then input.flat_map{|x| advance_hash_flattener(x, parent)}
    else [parent.join('_>'), input]
  end
end

#Some small transformations for the last step:
first_step =  advance_hash_flattener(my_hash)
   .each_slice(2)
   .group_by{|x| x.first}
   .map{|x| [x.first, x.last.map(&:last)]}
p first_step #=> [["x", [333]], ["y", [13]], ["z", [1, 2, 10, 20]], ["z_>zz", [40, 50]], ["a_>o", ["1"]], ["a_>p", ["2"]]]

# Create an array of Hashes:
final_array = [Hash.new]
first_step.each do |key,values|
  new = []
  values.each do |val|
    if final_array.first.key?(key)
      final_copy = final_array.map{|x|x.clone}
      final_copy.each{|x| x[key] = val}
      new += final_copy
    else
      final_array.each{|x| x[key] = val}
    end
  end
  final_array += new
end
# result stored in final_array

Upvotes: 3

Related Questions