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

Posted

技术标签:

【中文标题】从哈希/ YAML 中删除所有空元素?【英文标题】:Removing all empty elements from a hash / YAML? 【发布时间】:2011-03-27 21:51:17 【问题描述】:

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

【问题讨论】:

【参考方案1】:

Rails 4.1 添加了 Hash#compact 和 Hash#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 来实现它。见here for the benchmark。

如果您想将其反向移植到 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

【讨论】:

漂亮整洁,但可能值得注意的是,与公认的答案不同,Rails 扩展不是递归的? 它忽略了空的哈希值。【参考方案2】:

使用hsh.delete_if。在您的具体情况下,例如:hsh.delete_if |k, v| v.empty?

【讨论】:

递归一:proc = Proc.new |k, v| v.kind_of?(Hash) ? (v.delete_if(&l); nil) : v.empty? ; hsh.delete_if(&proc) 我相信您的正确答案中有错字:proc = Proc.new |k, v| v.kind_of?(Hash) ? (v.delete_if(&proc); nil) : v.empty? ; hsh.delete_if(&proc) @BSeven 他们似乎听到了你的声音! api.rubyonrails.org/classes/Hash.html#method-i-compact (Rails 4.1) 如果v 为nil,这将抛出NoMethodError 你可以使用 .delete_if |k, v| v.空白? 【参考方案3】:

你可以像这样向 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

【讨论】:

compact 应该只删除 nil。非虚假值 这里有一个问题:Hash#delete_if 是破坏性操作,而compact 方法不会修改对象。您可以使用Hash#reject。或者调用方法Hash#compact! 请注意 compactcompact! 在 Ruby => 2.4.0 和 Rails => 4.1 中是标准的。但它们是非递归的。 递归版本不适用于HashWithIndifferentAccess.. 在***.com/a/53958201/1519240查看我的版本 查看我的基于标准compacttransform_values的递归实现!【参考方案4】:

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 

这是link to the docs 和link to the relative PR。

还提供破坏性变体。见Hash#compact_blank!


如果您只需要删除 nil 值,

请考虑使用 Ruby 内置的 Hash#compactHash#compact! 方法。

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

【讨论】:

【参考方案5】:

如果您使用的是 Ruby 2.4+,您可以致电 compactcompact!

h =  a: 1, b: false, c: nil 
h.compact! #=>  a: 1, b: false 

https://ruby-doc.org/core-2.4.0/Hash.html#method-i-compact-21

【讨论】:

我还觉得有用的是Hash.except(:key) method【参考方案6】:

这个也会删除空哈希:

swoop = Proc.new  |k, v| v.delete_if(&swoop) if v.kind_of?(Hash);  v.empty? 
hsh.delete_if &swoop

【讨论】:

rails 版本,也适用于数组、哈希或字符串以外的其他类型的值(如 Fixnum):swoop = Proc.new |k, v| v.delete_if(&swoop) if v.kind_of?(Hash); v.blank? 【参考方案7】:

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

# 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"

【讨论】:

仅供参考:.empty? 会抛出数字错误,因此您可以在 Rails 中使用 .blank?【参考方案8】:

适用于哈希和数组

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(something)

【讨论】:

【参考方案9】:

我知道这个线程有点旧,但我想出了一个更好的解决方案,它支持多维散列。它使用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

【讨论】:

【参考方案10】:

我为此创建了一个 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

【讨论】:

【参考方案11】:

Ruby 的 Hash#compactHash#compact!Hash#delete_if! 不适用于嵌套的 nilempty? 和/或 blank? 值。请注意,后两种方法是破坏性的,所有nil""false[] 值都计为blank?

Hash#compactHash#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 != false 替换为v.nil? || v == "" 以严格删除空字符串和nil 值 将v.blank? && v != false 替换为v.nil? 以严格删除nil 值 等

于 2017 年 3 月 15 日编辑以保留 false 值并提供其他选项

【讨论】:

【参考方案12】:

我们的版本: 它还清除空字符串和 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

【讨论】:

【参考方案13】:

在用于删除 Hash 中的空值的 Simple one liner 中,

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

【讨论】:

小心,blank? 也适用于空字符串【参考方案14】:

可以使用facets 库(标准库中缺少的功能)来完成,如下所示:

require 'hash/compact'
require 'enumerable/recursively'
hash.recursively  |v| v.compact! 

适用于任何 Enumerable(包括数组、哈希)。

看看recursively method是如何实现的。

【讨论】:

【参考方案15】:

https://***.com/a/14773555/1519240 的递归版本有效,但不适用于 HashWithIndifferentAccess 或其他类似 Hash 的类。

这是我正在使用的版本:

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) 将接受更多类似于 Hash 的类。

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

【讨论】:

【参考方案16】:

我相信最好使用自递归方法。这样,它就可以根据需要深入。如果值为 nil 或空 Hash,这将删除键值对。

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。【参考方案17】:
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

【讨论】:

注意“当 Hash 然后 compact(val).empty?”应该是“当 Hash 然后 val.compact.empty?”【参考方案18】:

试试这个来删除 nil

hash =  a: true, b: false, c: nil 
=> :a=>true, :b=>false, :c=>nil
hash.inject()|c, (k, v)| c[k] = v unless v.nil?; c
=> :a=>true, :b=>false

【讨论】:

或者干脆hash.compact!【参考方案19】:

这是我有的东西:

# 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

【讨论】:

【参考方案20】:

从哈希中深度删除 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

【讨论】:

【参考方案21】:

在 ruby​​ 2.7 中有标准的 compacttransform_values 方法,在 Hash 上,所以你可以这样做:

class Hash 
  def deep_compact
    compact.transform_values|vl| vl.is_a?(Hash) ? vl.deep_compact : vl 
  end
end

这是最简洁的实现,恕我直言。

【讨论】:

以上是关于从哈希/ YAML 中删除所有空元素?的主要内容,如果未能解决你的问题,请参考以下文章

如何一次性删除通用列表中的所有空元素?

Javascript 删除所有空的 innerHTML 子元素

yaml数组类型详解

从哈希表中删除条目的最佳方法

如何有效地从 ArrayList 或字符串数​​组中删除所有空元素?

从 SQL Server 中的 XML 中删除所有空节点