将哈希表转换为嵌套哈希表

5
这个问题是这个问题的相反方向。
给定一个哈希表,每个键都有一个数组,例如:
{
    [:a, :b, :c] => 1,
    [:a, :b, :d] => 2,
    [:a, :e] => 3,
    [:f] => 4,
}

什么是将它转换为嵌套哈希表的最佳方法?
{
    :a => {
       :b => {:c => 1, :d => 2},
       :e => 3,
    },
    :f => 4,
}

这与递归无关,只是简单的嵌套。 - Dominik Honnef
@dominikh 这个特定的哈希可能不是递归的,但生成它的算法将是递归的。但我认为你说的有道理。我已经编辑过了。 - sawa
3
您尝试过什么?你的解决方案具体哪些地方不起作用? - maerics
6个回答

4
这里有一种迭代解决方案,递归解决方案留给读者自己练习:
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}}

谢谢。你的答案比递归做法好多了。太棒了。 - sawa
为什么默认使用空的哈希表?注意,通过更改为h.each_with_object({}) do |(k,v),ret|,您可以删除第一行和最后一行(虽然我不确定在回答问题时是否可以使用v1.9+中的“each_with_object”)。当然,您也可以使用reduce - Cary Swoveland

3

函数式递归算法:

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}

1

已经有一个好答案了,但是我尝试了这个递归解决方案,所以在这里提供:

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

对于混合哈希/数组嵌套结构,您可以使用以下代码。(已修改以适用于数组)

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

如果您使用以下内容进行扁平化处理(使用点号分隔键字符串而不是数组[如果需要,可以在点号上拆分])

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

使用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

另一种方式:

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

试一下:

h = {
    [:a, :b, :c] => 1,
    [:a, :b, :d] => 2,
    [:a, :e] => 3,
    [:f] => 4,
}

convert(h) #=> {:a=>{:b=>{:d=>2}, :e=>3},
           #    :f=>4}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接