Converting a hash into a nested hash

后端 未结 6 1713
后悔当初
后悔当初 2020-12-11 07:20

This question is the inverse of this question.

Given a hash that has an array for each key like

{
    [:a, :b, :c] => 1,
    [:a, :b, :d] => 2,         


        
相关标签:
6条回答
  • 2020-12-11 07:21

    For a mixed hash/array nested structure you can use this. (Modified for arrays as well)

    def unflatten(h={})
      ret = {}
      h.each do |k,v|
        node = ret
        keys = k.split('.').collect { |x| x.to_i.to_s == x ? x.to_i : x }
        keys.each_cons(2) do |x, next_d|
          if(next_d.is_a? Fixnum)
            node[x] ||= []
            node=node[x]
          else
            node[x] ||={}
            node=node[x]
          end
        end                                                                                                                                                                                                                                                                         
        node[keys[-1]] = v
      end
      ret
    end
    

    provided you used the below for flattening. ( dot separate string for key instead of array [split on . if you need] )

    def flatten_hash(hash)
      hash.each_with_object({}) do |(k, v), h|
        if v.is_a? Hash
          flatten_hash(v).map do |h_k, h_v|
            h["#{k}.#{h_k}"] = h_v 
          end 
        elsif v.is_a? Array
          flatten_array(v).map do |h_k,h_v|
            h["#{k}.#{h_k}"] = h_v 
          end 
        else
          h[k] = v 
        end 
      end 
    end
    
    
    def flatten_array(array)
      array.each_with_object({}).with_index do |(v,h),i|
        pp v,h,i
        if v.is_a? Hash
          flatten_hash(v).map do |h_k, h_v|
            h["#{i}.#{h_k}"] = h_v 
          end 
        elsif v.is_a? Array
          flatten_array(v).map do |h_k,h_v|
            h["#{i}.#{h_k}"] = h_v 
          end 
        end 
      end 
    end  
    
    0 讨论(0)
  • 2020-12-11 07:31

    Another way:

    def convert(h)
      h.each_with_object({}) { |(a,n),f| f.update({ a.first=>(a.size==1 ? n :
        convert({ a[1..-1]=>n })) }) { |_,ov,nv| ov.merge(nv) } }
    end
    

    Try it:

    h = {
        [:a, :b, :c] => 1,
        [:a, :b, :d] => 2,
        [:a, :e] => 3,
        [:f] => 4,
    }
    
    convert(h) #=> {:a=>{:b=>{:d=>2}, :e=>3},
               #    :f=>4}
    
    0 讨论(0)
  • 2020-12-11 07:34

    Functional recursive algorithm:

    require 'facets'
    
    class Hash
      def nestify
        map_by { |ks, v| [ks.first, [ks.drop(1), v]] }.mash do |key, pairs|
          [key, pairs.first[0].empty? ? pairs.first[1] : Hash[pairs].nestify]
        end
      end
    end
    
    p {[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4}.nestify
    # {:a=>{:b=>{:c=>1, :d=>2}, :e=>3}, :f=>4}
    
    0 讨论(0)
  • 2020-12-11 07:38

    Here's an iterative solution, a recursive one is left as an exercise to the reader:

    def convert(h={})
      ret = {}
      h.each do |k,v|
        node = ret
        k[0..-2].each {|x| node[x]||={}; node=node[x]}
        node[k[-1]] = v
      end
      ret
    end
    
    convert(your_hash) # => {:f=>4, :a=>{:b=>{:c=>1, :d=>2}, :e=>3}}
    
    0 讨论(0)
  • 2020-12-11 07:42

    There is already a good answer, but I worked on this recursive solution, so here it is:

    def to_nest(hash)
      {}.tap do |nest|
        hash.each_pair do |key, value|
          nodes = key.dup
          node  = nodes.shift
          if nodes.empty?
            nest[node] = value
          else
            nest[node] ||= {}
            nest[node].merge!({nodes => value})
          end
        end
        nest.each_pair do |key, value|
          nest[key] = to_nest(value) if value.kind_of?(Hash)
        end
      end
    end
    
    0 讨论(0)
  • 2020-12-11 07:48

    Using DeepEnumerable:

    require DeepEnumerable
    
    h = {[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4}
    
    h.inject({}){|hash, kv| hash.deep_set(*kv)}
    
    0 讨论(0)
提交回复
热议问题