Как мне удалить все пустые элементы (пустые элементы списка) из вложенного файла Hash или YAML?
Как мне удалить все пустые элементы (пустые элементы списка) из вложенного файла Hash или YAML?
Ответы:
Вы можете добавить компактный метод в 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
В Rails 4.1 добавлены Hash # compact и Hash # compact! в качестве основного расширения Ruby'sHash
класса . Вы можете использовать их так:
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
# => {}
Heads: эта реализация не является рекурсивной. Любопытно, что они реализовали это, используя #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
Используйте 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)
NoMethodError
если v
ноль.
Если вы используете 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 для удаления пустых пар ключ / значение из рубинового 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"}
.empty?
выдает ошибку для чисел, так что вы можете использовать .blank?
вRails
работает как для хэшей, так и для массивов
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)
Я знаю, что эта ветка немного устарела, но я нашел лучшее решение, которое поддерживает многомерные хеши. Он использует 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, который рекурсивно отфильтровывает ноль записей (и, необязательно, пустые записи):
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
Руби 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/03/15, чтобы сохранить false
значения и представить другие варианты
В Simple один вкладыш для удаления нулевых значений в Hash,
rec_hash.each {|key,value| rec_hash.delete(key) if value.blank? }
blank?
подходит и для пустых строк
Может быть сделано с библиотекой граней (отсутствующей в стандартной библиотеке):
require 'hash/compact'
require 'enumerable/recursively'
hash.recursively { |v| v.compact! }
Работает с любым Enumerable (включая Array, 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
Попробуйте это, чтобы удалить ноль
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!
Рекурсивная версия 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)
будет принимать больше классов, которые похожи на хэш.
Вы также можете заменить inject({})
на, inject(HashWithIndifferentAccess.new)
если хотите получить доступ к новому хешу, используя как символ, так и строку.
Вот что у меня есть:
# 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
Глубокое удаление нулевых значений из хеша.
# 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
Если вы используете 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#compact
и Hash#compact!
методов.
{ a: 1, b: false, c: nil }.compact
# => { a: 1, b: false }