2012-03-10 95 views
24

这个问题的this question倒数。转换嵌套散列成扁平散列

给定一个嵌套的哈希像

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

是什么把它转换成一个平坦的哈希像

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

所以你想要一个键数组以便产生一个值? – Linuxios 2012-03-10 16:59:06

回答

15

的另一种方法:

def flat_hash(h,f=[],g={}) 
    return g.update({ f=>h }) unless h.is_a? Hash 
    h.each { |k,r| flat_hash(r,f+[k],g) } 
    g 
end 

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

flat_hash(h) #=> {[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4} 
+0

这是非常快的。 – sawa 2014-05-30 15:06:37

+0

我知道Ruby不一定支持开箱即用的TCO,但是如果您在这里没有返回'g',这是否会进行尾部优化? – rusty 2015-01-30 15:27:54

+0

我听说过“tail-call optimized”一词,但我不知道它的含义。也许一个计算机科学家可以回答你的问题。 – 2015-01-31 00:59:28

5

最好的办法,这不是一个试图给你最好办法做到这一点,但它是一种方法:P

def flatten(hash) 
    return {[] => hash} if !hash.is_a?(Hash) 
    map = {} 
    hash.each_pair do |key1, value1| 
    flatten(value1).each_pair do |key2, value2| 
     map[[key1] + key2] = value2 
    end 
    end 
    return map 
end 

它适用于您的例如,产生这样的结果:

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

它可能不会产生您期望的结果,如果有空散列。

15

非常相似,Adiel Mittmann的解决方案

def flat_hash(h, k = []) 
    new_hash = {} 
    h.each_pair do |key, val| 
    if val.is_a?(Hash) 
     new_hash.merge!(flat_hash(val, k + [key])) 
    else 
     new_hash[k + [key]] = val 
    end 
    end 
    new_hash 
end 

编辑:重构高雅。应该几乎一样快。

def flat_hash(hash, k = []) 
    return {k => hash} unless hash.is_a?(Hash) 
    hash.inject({}){ |h, v| h.merge! flat_hash(v[-1], k + [v[0]]) } 
end 
+0

这一个跑的最快。谢谢。 – sawa 2012-03-10 17:38:21

+1

@sawa:未来只是一个提示:如果你想有一个快速的解决方案,在接下来的时间问题提到这一点。通常,Python或Ruby等动态语言的主要标准是优雅和简洁。如果你特别要求的性能,以及,你可以得到更适合的答案:) – 2012-03-10 17:43:44

+1

@sawa:重构高雅。 – Kyle 2012-03-10 19:18:46

8

我尝试:

def flatten_hash(h) 
    return { [] => h } unless h.is_a?(Hash) 
    Hash[h.map { |a,v1| flatten_hash(v1).map { |b,v2| [[a] + b, v2] } }.flatten(1)] 
end 

很抱歉的坏变量的名称,必须适应它在同一行。

3

的功能的方法(参见history一种替代的实施方式):

def recursive_flatten(hash) 
    hash.flat_map do |key, value| 
    if value.is_a?(Hash) 
     recursive_flatten(value).map { |ks, v| [[key] + ks, v] } 
    else 
     [[[key], value]] 
    end 
    end.to_h 
end 
1

通过@卡里-swoveland方式的启发,但在哈希类:

class Hash 
    def deep_flatten(previous_key=[]) 
    flat_hash = {} 
    self.each do |key, value| 
     next_key = previous_key+[key] 
     flat_hash.update(value.is_a?(Hash) ? value.deep_flatten(next_key) : {next_key=>value}) 
    end 
    return flat_hash 
    end 
end 

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

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

声明性溶液使用DeepEnumerable

require 'deep_enumerable' 

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

h.deep_each.map do |k, v| 
    [DeepEnumerable.deep_key_to_array(k), v] 
end.to_h 

,或者为那些喜欢谁点自由式

h.deep_each.to_h.shallow_map_keys(&DeepEnumerable.method(:deep_key_to_array)) 
1

阵列支持/可读的名称/速度/字符串化的结果键没有更新

def flat_hash(input, base = nil, all = {}) 
    if input.is_a?(Array) 
    input = input.each_with_index.to_a.each(&:reverse!) 
    end 

    if input.is_a?(Hash) || input.is_a?(Array) 
    input.each do |k, v| 
     flat_hash(v, base ? "#{base}.#{k}" : k, all) 
    end 
    else 
    all[base] = input 
    end 

    all 
end