从哈希/ YAML 中删除所有空元素?

163

我该如何从嵌套的哈希或 YAML 文件中删除所有空元素(空列表项)?

22个回答

182

Rails 4.1将 Hash#compactHash#compact! 添加到Ruby的Hash类中作为核心扩展。您可以像这样使用它们:

hash = { a: true, b: false, c: nil }
hash.compact                        
# => { a: true, b: false }
hash                                
# => { a: true, b: false, c: nil }
hash.compact!                        
# => { a: true, b: false }
hash                                
# => { a: true, b: false }
{ c: nil }.compact                  
# => {}

注意:此实现不是递归的。有趣的是,为了提高性能,他们使用了#select而不是#delete_if来实现它。请参见这里的基准测试

如果你想将它移植到你的Rails 3应用中:

# config/initializers/rails4_backports.rb

class Hash
  # as implemented in Rails 4
  # File activesupport/lib/active_support/core_ext/hash/compact.rb, line 8
  def compact
    self.select { |_, value| !value.nil? }
  end
end

3
整洁明了,但值得注意的是,与被接受的答案不同,Rails扩展并不是递归的。 - SirRawlins
2
它省略了空哈希。 - Sebastián Palma

148

使用hsh.delete_if方法。在你的特定情况下,可以这样写:hsh.delete_if { |k, v| v.empty? }


7
递归式的代码:proc = Proc.new { |k, v| v.kind_of?(Hash) ? (v.delete_if(&l); nil) : v.empty? }; hsh.delete_if(&proc) 可以翻译为:# 定义一个名为proc的Proc对象,删除哈希表中所有值为空或者为哈希表的键值对 proc = Proc.new { |k, v| v.kind_of?(Hash) ? (v.delete_if(&proc); nil) : v.empty? } # 使用proc对象作为参数调用哈希表的delete_if方法 hsh.delete_if(&proc) - Daniel O'Hara
3
我相信您的答案基本正确,只是有一个错别字:proc = Proc.new { |k, v| v.kind_of?(Hash) ? (v.delete_if(&proc); nil) : v.empty? }; hsh.delete_if(&proc)。 - acw
如果在哈希表中添加#compact会很棒。 - B Seven
3
似乎他们听到了@BSeven的建议!http://api.rubyonrails.org/classes/Hash.html#method-i-compact (适用于Rails 4.1) - dgilperez
2
如果v是nil,这将抛出一个NoMethodError - Jerrod
6
你可以使用“.delete_if { |k, v| v.blank? }” - Serhii Nadolynskyi

74

你可以像这样为哈希表添加一个紧凑的方法

class Hash
  def compact
    delete_if { |k, v| v.nil? }
  end
end

或者选择支持递归的版本。

class Hash
  def compact(opts={})
    inject({}) do |new_hash, (k,v)|
      if !v.nil?
        new_hash[k] = opts[:recurse] && v.class == Hash ? v.compact(opts) : v
      end
      new_hash
    end
  end
end

2
压缩应该只删除 nil 值,而不是 falsy 值。 - Ismael
2
这里有一个问题:Hash#delete_if是一种破坏性操作,而compact方法不会修改对象。你可以使用Hash#reject。或者调用Hash#compact!方法。 - tokland
5
请注意,compactcompact! 在 Ruby => 2.4.0 和 Rails => 4.1 中已经成为标准方法。但请注意它们不是递归的。 - aidan
递归版本与HashWithIndifferentAccess不兼容。请查看我的版本:https://dev59.com/CXA75IYBdhLWcg3wJFgL#53958201 - user1519240
请查看我基于标准的compacttransform_values编写的递归实现! - Алексей Лещук

47

compact_blank (Rails 6.1+)

若你使用的是Rails(或者是独立的ActiveSupport),从版本6.1开始,可以使用compact_blank方法来从散列中移除blank值。

它在底层使用Object#blank?来确定项是否为空。

{ a: "", b: 1, c: nil, d: [], e: false, f: true }.compact_blank
# => { b: 1, f: true }

这里有一个文档链接和一个与之相关的PR链接

也提供了一种破坏性变体。请参阅Hash#compact_blank!


如果您只需删除nil值,请考虑使用Ruby内置的Hash#compactHash#compact!方法。

{ a: 1, b: false, c: nil }.compact
# => { a: 1, b: false }

13

1
我也发现有用的是 Hash.except(:key) 方法 - FireDragon

7
你可以使用Hash#reject从Ruby Hash中删除空的键值对。
# Remove empty strings
{ a: 'first', b: '', c: 'third' }.reject { |key,value| value.empty? } 
#=> {:a=>"first", :c=>"third"}

# Remove nil
{a: 'first', b: nil, c: 'third'}.reject { |k,v| v.nil? } 
# => {:a=>"first", :c=>"third"}

# Remove nil & empty strings
{a: '', b: nil, c: 'third'}.reject { |k,v| v.nil? || v.empty? } 
# => {:c=>"third"}

4
FYI:在Rails中,对于数字,.empty?会抛出错误,因此您可以使用.blank? - Shiva

7
这个会同时删除空的哈希表:
swoop = Proc.new { |k, v| v.delete_if(&swoop) if v.kind_of?(Hash);  v.empty? }
hsh.delete_if &swoop

1
Rails版本,也适用于除了Array、Hash或String(如Fixnum)之外的其他类型的值:swoop = Proc.new { |k, v| v.delete_if(&swoop) if v.kind_of?(Hash); v.blank? } - wdspkr

5

适用于哈希表和数组

module Helpers
  module RecursiveCompact
    extend self

    def recursive_compact(hash_or_array)
      p = proc do |*args|
        v = args.last
        v.delete_if(&p) if v.respond_to? :delete_if
        v.nil? || v.respond_to?(:"empty?") && v.empty?
      end

      hash_or_array.delete_if(&p)
    end
  end
end

注:根据某人的回答,找不到

用法 - Helpers::RecursiveCompact.recursive_compact(某物)


(涉及IT技术)

4
我为此编写了一个 deep_compact 方法,它可以递归地过滤掉 nil 记录(可选择过滤空记录):
class Hash
  # Recursively filters out nil (or blank - e.g. "" if exclude_blank: true is passed as an option) records from a Hash
  def deep_compact(options = {})
    inject({}) do |new_hash, (k,v)|
      result = options[:exclude_blank] ? v.blank? : v.nil?
      if !result
        new_value = v.is_a?(Hash) ? v.deep_compact(options).presence : v
        new_hash[k] = new_value if new_value
      end
      new_hash
    end
  end
end

4

我知道这篇文章有些旧了,但我提出了一种更好的解决方案,支持多维哈希。它使用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

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