我正在尝试编写一种方法,该方法删除嵌套哈希中递归指向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"}}
?
诀窍是递归压缩嵌套哈希,然后消除空值。
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"}}
发现,通过将递归函数调用放在块的末尾,我大部分时间都在那里。(这是"尾端"递归吗?
我还对transform_values
返回的哈希调用reject
以删除任何空对。
这实现了我想要的:
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"}}
使用 Hash#reject! 的其他选项,它会更改原始 Hash:
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"}}