【发布时间】:2011-03-27 21:51:17
【问题描述】:
如何从嵌套的 Hash 或 YAML 文件中删除所有空元素(空列表项)?
【问题讨论】:
标签: ruby-on-rails ruby hash yaml
如何从嵌套的 Hash 或 YAML 文件中删除所有空元素(空列表项)?
【问题讨论】:
标签: ruby-on-rails ruby hash yaml
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
【讨论】:
使用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)
v 为nil,这将抛出NoMethodError。
你可以像这样向 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
【讨论】:
Hash#delete_if 是破坏性操作,而compact 方法不会修改对象。您可以使用Hash#reject。或者调用方法Hash#compact!。
compact 和 compact! 在 Ruby => 2.4.0 和 Rails => 4.1 中是标准的。但它们是非递归的。
HashWithIndifferentAccess.. 在stackoverflow.com/a/53958201/1519240查看我的版本
compact和transform_values的递归实现!
如果您使用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#compact 和 Hash#compact! 方法。
{ a: 1, b: false, c: nil }.compact
# => { a: 1, b: false }
【讨论】:
如果您使用的是 Ruby 2.4+,您可以致电 compact 和 compact!
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
【讨论】:
这个也会删除空哈希:
swoop = Proc.new { |k, v| v.delete_if(&swoop) if v.kind_of?(Hash); v.empty? }
hsh.delete_if &swoop
【讨论】:
swoop = Proc.new { |k, v| v.delete_if(&swoop) if v.kind_of?(Hash); v.blank? }
您可以使用 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?
适用于哈希和数组
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)
【讨论】:
我知道这个线程有点旧,但我想出了一个更好的解决方案,它支持多维散列。它使用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
【讨论】:
我为此创建了一个 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
【讨论】:
Ruby 的 Hash#compact、Hash#compact! 和 Hash#delete_if! 不适用于嵌套的 nil、empty? 和/或 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 != false 替换为v.nil? || v == "" 以严格删除空字符串和nil 值v.blank? && v != false 替换为v.nil? 以严格删除nil 值于 2017 年 3 月 15 日编辑以保留 false 值并提供其他选项
【讨论】:
我们的版本: 它还清除空字符串和 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
【讨论】:
在用于删除 Hash 中的空值的 Simple one liner 中,
rec_hash.each {|key,value| rec_hash.delete(key) if value.blank? }
【讨论】:
blank? 也适用于空字符串
可以使用facets 库(标准库中缺少的功能)来完成,如下所示:
require 'hash/compact'
require 'enumerable/recursively'
hash.recursively { |v| v.compact! }
适用于任何 Enumerable(包括数组、哈希)。
看看recursively method是如何实现的。
【讨论】:
https://stackoverflow.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)。
【讨论】:
我相信最好使用自递归方法。这样,它就可以根据需要深入。如果值为 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
【讨论】:
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
【讨论】:
试试这个来删除 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!
这是我有的东西:
# 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
【讨论】:
从哈希中深度删除 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
【讨论】:
在 ruby 2.7 中有标准的 compact 和 transform_values 方法,在 Hash 上,所以你可以这样做:
class Hash
def deep_compact
compact.transform_values{|vl| vl.is_a?(Hash) ? vl.deep_compact : vl }
end
end
这是最简洁的实现,恕我直言。
【讨论】: