Turning a Hash of Arrays into an Array of Hashes in Ruby

后端 未结 7 1738
走了就别回头了
走了就别回头了 2020-12-30 07:19

We have the following datastructures:

{:a => [\"val1\", \"val2\"], :b => [\"valb1\", \"valb2\"], ...}

And I want to turn that into

相关标签:
7条回答
  • 2020-12-30 07:42

    Let's look closely what the data structure we are trying to convert between:

    #Format A
    [
     ["val1", "val2"],          :a
     ["valb1", "valb2"],        :b 
     ["valc1", "valc2"]         :c 
    ]
    #Format B
    [ :a        :b       :c
     ["val1", "valb1", "valc1"],
     ["val2", "valb2", "valc3"]
    ]
    

    It is not diffculty to find Format B is the transpose of Format A in essential , then we can come up with this solution:

    h={:a => ["vala1", "vala2"], :b => ["valb1", "valb2"], :c => ["valc1", "valc2"]}
    sorted_keys =  h.keys.sort_by {|a,b| a.to_s <=> b.to_s}
    
    puts sorted_keys.inject([])  {|s,e| s << h[e]}.transpose.inject([])   {|r, a| r << Hash[*sorted_keys.zip(a).flatten]}.inspect
    #[{:b=>"valb1", :c=>"valc1", :a=>"vala1"}, {:b=>"valb2", :c=>"valc2", :a=>"vala2"}]
    
    0 讨论(0)
  • 2020-12-30 07:51
    m = {}
    a,b = Array(h).transpose
    b.transpose.map { |y| [a, y].transpose.inject(m) { |m,x| m.merge Hash[*x] }}
    
    0 讨论(0)
  • 2020-12-30 07:53

    My attempt, perhaps slightly more compact.

    h = { :a => ["val1", "val2"], :b => ["valb1", "valb2"] }
    
    h.values.transpose.map { |s| Hash[h.keys.zip(s)] }
    

    Should work in Ruby 1.9.3 or later.


    Explanation:

    First, 'combine' the corresponding values into 'rows'

    h.values.transpose
    # => [["val1", "valb1"], ["val2", "valb2"]] 
    

    Each iteration in the map block will produce one of these:

    h.keys.zip(s)
    # => [[:a, "val1"], [:b, "valb1"]]
    

    and Hash[] will turn them into hashes:

    Hash[h.keys.zip(s)]
    # => {:a=>"val1", :b=>"valb1"}      (for each iteration)
    
    0 讨论(0)
  • 2020-12-30 07:56

    You could use inject to build an array of hashes.

    hash = { :a => ["val1", "val2"], :b => ["valb1", "valb2"] }
    array = hash.inject([]) do |pairs, pair|
      pairs << { pair[0] => pair[1] }
      pairs
    end
    array.inspect # => "[{:a=>["val1", "val2"]}, {:b=>["valb1", "valb2"]}]"
    

    Ruby documentation has a few more examples of working with inject.

    0 讨论(0)
  • 2020-12-30 08:02

    This will work assuming all the arrays in the original hash are the same size:

    hash_array = hash.first[1].map { {} }
    hash.each do |key,arr|
      hash_array.zip(arr).each {|inner_hash, val| inner_hash[key] = val}
    end
    
    0 讨论(0)
  • 2020-12-30 08:03

    Using a functional approach (see Enumerable):

    hs = h.values.transpose.map { |vs| h.keys.zip(vs).to_h }
    #=> [{:a=>"val1", :b=>"valb1"}, {:a=>"val2", :b=>"valb2"}]
    

    And back:

    h_again = hs.first.keys.zip(hs.map(&:values).transpose).to_h
    #=> {:a=>["val1", "val2"], :b=>["valb1", "valb2"]}
    
    0 讨论(0)
提交回复
热议问题