如何递归地从(YAML)哈希中删除所有空值键?

9
我一直在尝试清除YAML文件中所有空值或空哈希值的哈希键。
这篇早期文章帮助我几乎做到了,但递归一行代码会在足够深的嵌套时留下空哈希。
我真的很感激任何帮助。谢谢!
proc = Proc.new { |k, v| (v.kind_of?(Hash) && !v.empty? ) ? (v.delete_if(&proc); nil) : v.blank? }

hash = {"x"=>{"m"=>{"n"=>{}}}, 'y' => 'content'}
hash.delete_if(&proc)

实际输出

 {"x"=>{"m"=>{}}, "y"=>"content"} 

期望输出

{"y"=>"content"}
6个回答

15
class Hash
  def delete_blank
    delete_if{|k, v| v.empty? or v.instance_of?(Hash) && v.delete_blank.empty?}
  end
end

p hash.delete_blank
# => {"y"=>"content"}

我的版本(Rails):class Hash def delete_blank delete_if{|k, v| v.blank? or (v.instance_of?(Hash) && v.delete_blank.empty?)} end end - Ivailo Bardarov
1
使用“blank?”而不是“empty?”,因为它会在nil时失败。 - msroot
你使用delete_if而不是reject的原因是什么? - Daniel Viglione

6
这里有一个更通用的方法:
class Hash
  def deep_reject(&blk)
    self.dup.deep_reject!(&blk)
  end

  def deep_reject!(&blk)
    self.each do |k, v|
      v.deep_reject!(&blk)  if v.is_a?(Hash)
      self.delete(k)  if blk.call(k, v)
    end
  end
end

{ a: 1, b: nil, c: { d: nil, e: '' } }.deep_reject! { |k, v| v.blank? }
==> { a: 1 }

4
我认为这是最正确的版本:
h = {a: {b: {c: "",}, d:1}, e:2, f: {g: {h:''}}}
p = proc do |_, v|
  v.delete_if(&p) if v.respond_to? :delete_if
  v.nil? || v.respond_to?(:"empty?") && v.empty?
end
h.delete_if(&p)
#=> {:a=>{:d=>1}, :e=>2}

@DavidWest p是一个递归的过程; 如果值v是类似映射对象(响应delete_if),则我们修剪其中所有空值; 如果v为空 (或为nil值),则移除它 (返回truedelete_if)。 这个解释够清楚吗? :) - Iazel
非常优雅。我印象深刻。我对&符号不太熟悉。为什么要使用它?在阅读完这篇文章后,我会更深入地了解procs。有没有一个关键词可以帮助我了解更多关于&符号的信息?它和&block一样吗? - David West
是的,它们几乎是相同的东西。当用于方法定义时,&block 捕获传递给方法的任何块并将其转换为 proc。当在参数之前使用时,它则相反:将 proc 转换为块。老实说,这些不是廉价的指令,如果可能的话应该避免使用 xD 但是,它们确实允许一些很好的技巧 ^^ - Iazel
如果我们的哈希表还包括值为哈希表数组的情况,该如何实现相同的功能呢?例如 h = {a: {b: {c: "",}, d:1}, e:2, f: {g: {h:''}}, j: [ {k: 1, l: nil } ]} - Andrei
2
没有任何变化,因为数组也有 #delete_if。这就是鸭子类型的威力! ;) - Iazel

1

我知道这个帖子有点老了,但是我想出了一个更好的解决方案,支持多维哈希。它使用delete_if?,除了它是多维的,并默认清除任何空值,如果传递了一个块,则通过其子代传递。

# Hash cleaner
class Hash
    def clean!
        self.delete_if do |key, val|
            if block_given?
                yield(key,val)
            else
                # Prepeare the tests
                test1 = val.nil?
                test2 = val === 0
                test3 = val === false
                test4 = val.empty? if val.respond_to?('empty?')
                test5 = val.strip.empty? if val.is_a?(String) && val.respond_to?('empty?')

                # Were any of the tests true
                test1 || test2 || test3 || test4 || test5
            end
        end

        self.each do |key, val|
            if self[key].is_a?(Hash) && self[key].respond_to?('clean!')
                if block_given?
                    self[key] = self[key].clean!(&Proc.new)
                else
                    self[key] = self[key].clean!
                end
            end
        end

        return self
    end
end

在发布复制和粘贴的样板/逐字回答多个问题时要小心,社区往往会将其标记为“垃圾邮件”。如果您这样做,通常意味着这些问题是重复的,请将它们标记为重复:https://dev59.com/CXA75IYBdhLWcg3wJFgL#12360142 - Kev

0

只是一个有点相关的事情。如果你想从嵌套哈希中删除指定的键:

def find_and_destroy(*keys)
    delete_if{ |k, v| (keys.include?(k.to_s) ? true : ( (v.each { |vv| vv = vv.find_and_destroy(*keys) }) if v.instance_of?(Array) ;  (v.each { |vv| vv = vv.find_and_destroy(*keys) }) if v.instance_of?(Hash); false) )}
end

你也可以进一步自定义它


0
hash = {"x"=>{"m"=>{"n"=>{}}}, 'y' => 'content'}
clean = proc{ |k,v| !v.empty? ? Hash === v ? v.delete_if(&clean) : false : true }
hash.delete_if(&clean)
#=> {"y"=>"content"} 

或者像@sawa建议的那样,你可以使用这个过程

clean = proc{ |k,v| v.empty? or Hash === v && v.delete_if(&clean) }

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