Ruby:如何从嵌套哈希中删除nil / empty值,为什么我的尝试不这样做?

时间:2019-06-05 08:42:23

标签: ruby recursion

我正在尝试编写一种方法,以删除嵌套哈希中所有递归指向nil的键。

例如:

{:a=>nil, :b=>"b", :c=>nil, :d=>{:dd=>"dd", :ee=>nil, :ff=>"ff"}, :e=>{:gg=>nil, :hh=>nil}}

成为:

{:b=>"b", :d=>{:dd=>"dd", :ff=>"ff"}}

虽然我运气不好。

我最近的尝试如下:

def deep_compact(hash)
  hash.reject do |key, value|
    deep_compact(value) if value.class == Hash
    next true if value.nil? || value.empty?
  end
end

在这里,我想遍历哈希中的每个键值对。如果该值是哈希值,则我希望对该哈希值执行相同的操作。如果值是nil或为空,我想拒绝该对。否则,我想保留它。

结果不是我想要的:

#=> {:b=>"b", :d=>{:dd=>"dd", :ee=>nil, :ff=>"ff"}, :e=>{:gg=>nil, :hh=>nil}}

我也尝试过:

def deep_compact(hash)
  hash.compact.transform_values do |value|
    deep_compact(value) if value.class == Hash
    value
  end
end

再次,我得到相同的结果:

#=> {:b=>"b", :d=>{:dd=>"dd", :ee=>nil, :ff=>"ff"}, :e=>{:gg=>nil, :hh=>nil}}

这使我相信我错过了某些东西,或者我对递归的理解是错误的。

我的尝试是否结束?我该怎么做才能确保得到想要的结果:{:b=>"b", :d=>{:dd=>"dd", :ff=>"ff"}}

3 个答案:

答案 0 :(得分:1)

诀窍是递归压缩嵌套的哈希,然后消除空值。

compact = ->(hash) { 
  hash.is_a?(Hash) ?
    hash.map { |k, v| [k, compact.(v)] }.
         to_h.
         delete_if { |_, v| v.nil? || v.respond_to?(:empty?) && v.empty? } :
    hash 
}
compact.(input)
#⇒ {:b=>"b", :d=>{:dd=>"dd", :ff=>"ff"}}

答案 1 :(得分:1)

我发现,通过将递归函数调用放在该块的末尾,可以使我得到大部分帮助。 (这是“尾端”递归吗?)

我还对reject返回的哈希值调用transform_values,以删除所有空对。

这实现了我想要的:

def deep_compact(hash)
  hash.compact.transform_values do |value|
    next value unless value.class == Hash
    deep_compact(value)
  end.reject { |_k, v| v.empty? }
end
> h
=> {:a=>nil, :b=>"b", :c=>nil, :d=>{:dd=>"dd", :ee=>nil, :ff=>"ff"}, :e=>{:gg=>nil, :hh=>nil}}
> deep_compact h
=> {:b=>"b", :d=>{:dd=>"dd", :ff=>"ff"}}

答案 2 :(得分:1)

使用Hash#reject!的其他选项,它将更改原始哈希:

def deep_compact(h)
  h.each { |_, v| deep_compact(v) if v.is_a? Hash }.reject! { |_, v| v.nil? || v.empty? }
end

deep_compact(h)
#=> {:b=>"b", :d=>{:dd=>"dd", :ff=>"ff"}}