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


Answers:


70

您可以像这样向Hash添加紧凑方法

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
紧凑应该只删除零。不是虚假的价值观
Ismael Abreu

1
这有一个问题:Hash#delete_if是破坏性的操作,而compact方法不会修改对象。您可以使用Hash#reject。或调用方法Hash#compact!
tokland

5
请注意,compact并且compact!在Ruby => 2.4.0和Rails => 4.1中成为标准配置。它们是非递归的。
艾丹71年

递归版本不适用于HashWithIndifferentAccess..在stackoverflow.com/a/53958201/1519240上
user1519240 '18

157

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
它省略了空散列。
塞巴斯蒂安·帕尔玛

142

使用hsh.delete_if。在您的特定情况下,类似:hsh.delete_if { |k, v| v.empty? }


6
递归一:proc = Proc.new { |k, v| v.kind_of?(Hash) ? (v.delete_if(&l); nil) : v.empty? }; hsh.delete_if(&proc)
Daniel O'Hara 2010年

3
我相信您的正确答案中有错别字:proc = Proc.new {| k,v | v.kind_of?(Hash)吗?(v.delete_if(&proc); nil):v.empty?}; hsh.delete_if(&proc)
acw 2011年


2
这将抛出NoMethodErrorif v为零。
杰罗德

6
您可以使用.delete_if {| k,v | v。空白?}
Serhii Nadolynskyi


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 2014年

6

您可以使用Hash#reject从红宝石哈希中删除空键/值对。

# 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:.empty?抛出错误数字,所以你可以用.blank?Rails
幻术师

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

PS基于某人的答案,找不到

用法- Helpers::RecursiveCompact.recursive_compact(something)


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

4

为此,我制作了一个deep_compact方法,该方法递归地过滤掉了零个记录(以及可选的空白记录):

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

Ruby的Hash#compactHash#compact!Hash#delete_if!没有对嵌套的工作nilempty?和/或blank?值。请注意,后两种方法是破坏性的,并且所有nil""false[]{}的值都算作blank?

Hash#compact并且Hash#compact!仅在Rails或Ruby 2.4.0及更高版本中可用。

这是一个非破坏性的解决方案,它删除所有空数组,哈希,字符串和nil值,同时保留所有false值:

blank?可以替换为nil?empty?根据需要替换。)

def remove_blank_values(hash)
  hash.each_with_object({}) do |(k, v), new_hash|
    unless v.blank? && v != false
      v.is_a?(Hash) ? new_hash[k] = remove_blank_values(v) : new_hash[k] = v
    end
  end
end

破坏性版本:

def remove_blank_values!(hash)
  hash.each do |k, v|
    if v.blank? && v != false
      hash.delete(k)
    elsif v.is_a?(Hash)
      hash[k] = remove_blank_values!(v)
    end
  end
end

或者,如果要将两个版本都添加为Hash类的实例方法,请执行以下操作:

class Hash
  def remove_blank_values
    self.each_with_object({}) do |(k, v), new_hash|
      unless v.blank? && v != false
        v.is_a?(Hash) ? new_hash[k] = v.remove_blank_values : new_hash[k] = v
      end
    end
  end

  def remove_blank_values!
    self.each_pair do |k, v|
      if v.blank? && v != false
        self.delete(k)
      elsif v.is_a?(Hash)
        v.remove_blank_values!
      end
    end
  end
end

其他选项:

  • 替换v.blank? && v != falsev.nil? || v == ""以严格删除空字符串并nil
  • 替换v.blank? && v != falsev.nil?以严格删除nil
  • 等等。

编辑于2017/03/15以保留false价值并提出其他选择


3

我们的版本:它还清除空字符串和nil值

class Hash

  def compact
    delete_if{|k, v|

      (v.is_a?(Hash) and v.respond_to?('empty?') and v.compact.empty?) or
          (v.nil?)  or
          (v.is_a?(String) and v.empty?)
    }
  end

end

3

在用于删除哈希中的空值的简单一类划线中,

rec_hash.each {|key,value| rec_hash.delete(key) if value.blank? } 

小心,也blank?应该使用空弦
Hertzel Guinness


0

我认为最好使用自递归方法。这样,它就可以深入到所需的深度。如果值是nil或空散列,则将删除键值对。

class Hash
  def compact
    delete_if {|k,v| v.is_a?(Hash) ? v.compact.empty? : v.nil? }
  end
end

然后使用它看起来像这样:

x = {:a=>{:b=>2, :c=>3}, :d=>nil, :e=>{:f=>nil}, :g=>{}}
# => {:a=>{:b=>2, :c=>3}, :d=>nil, :e=>{:f=>nil}, :g=>{}} 
x.compact
# => {:a=>{:b=>2, :c=>3}}

要保留空散列,可以将其简化为。

class Hash
  def compact
    delete_if {|k,v| v.compact if v.is_a?(Hash); v.nil? }
  end
end

嗯。循环引用可能会导致无限循环IIUC。
Hertzel Guinness

0
class Hash   
  def compact
    def _empty?(val)
      case val
      when Hash     then val.compact.empty?
      when Array    then val.all? { |v| _empty?(v) }
      when String   then val.empty?
      when NilClass then true
      # ... custom checking 
      end
    end

    delete_if { |_key, val| _empty?(val) }   
  end 
end

请注意,“何时哈希然后compact(val).empty?” 应该是“何时哈希然后val.compact.empty?”
AlexITC


0

https://stackoverflow.com/a/14773555/1519240的递归版本适用,但不适用于HashWithIndifferentAccessHash或其他类。

这是我使用的版本:

def recursive_compact
  inject({}) do |new_hash, (k,v)|
    if !v.nil?
      new_hash[k] = v.kind_of?(Hash) ? v.recursive_compact : v
    end
    new_hash
  end
end

kind_of?(Hash) 将接受更多类似于哈希的类。

如果您想同时使用符号和字符串访问新的哈希,也可以用替换inject({})inject(HashWithIndifferentAccess.new)


0

这是我的一些东西:

# recursively remove empty keys (hashes), values (array), hashes and arrays from hash or array
def sanitize data
  case data
  when Array
    data.delete_if { |value| res = sanitize(value); res.blank? }
  when Hash
    data.delete_if { |_, value| res = sanitize(value); res.blank? }
  end
  data.blank? ? nil : data
end

0

从哈希中深度删除nil个值。

  # returns new instance of hash with deleted nil values
  def self.deep_remove_nil_values(hash)
    hash.each_with_object({}) do |(k, v), new_hash|
      new_hash[k] = deep_remove_nil_values(v) if v.is_a?(Hash)
      new_hash[k] = v unless v.nil?
    end
  end

  # rewrite current hash
  def self.deep_remove_nil_values!(hash)
    hash.each do |k, v|
      deep_remove_nil_values(v) if v.is_a?(Hash)
      hash.delete(k) if v.nil?
    end
  end

0

如果您使用Rails(或独立使用ActiveSupport),则从version开始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 }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.