Como eu removeria todos os elementos vazios (itens de lista vazios) de um arquivo Hash ou YAML aninhado?
Como eu removeria todos os elementos vazios (itens de lista vazios) de um arquivo Hash ou YAML aninhado?
Respostas:
Você pode adicionar um método compacto ao Hash assim
class Hash
def compact
delete_if { |k, v| v.nil? }
end
end
ou para uma versão que suporte recursão
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
é uma operação destrutiva, enquanto os compact
métodos não modificam o objeto. Você pode usar Hash#reject
. Ou chamar o método Hash#compact!
.
compact
e compact!
é padrão no Ruby => 2.4.0 e Rails => 4.1. Eles não são recursivos.
HashWithIndifferentAccess
.. Verifique minha versão em stackoverflow.com/a/53958201/1519240
O Rails 4.1 adicionou o Hash # compact e o Hash # compact! como extensões principais da Hash
classe Ruby . Você pode usá-los assim:
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
# => {}
Atenção: essa implementação não é recursiva. Como curiosidade, eles o implementaram usando em #select
vez de #delete_if
por razões de desempenho. Veja aqui o benchmark .
Caso você queira fazer o backport para seu aplicativo 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
Use hsh.delete_if . No seu caso específico, algo como: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
if v
é nulo.
Se você usa o Ruby 2.4+, pode ligar compact
ecompact!
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
Este também excluiria hashes vazios:
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? }
Você pode usar o Hash # rejeitar para remover pares de chave / valor vazios de um Hash 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?
lança erro para números, para que você possa usar .blank?
emRails
funciona para hashes e matrizes
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 com base na resposta de alguém, não consigo encontrar
uso - Helpers::RecursiveCompact.recursive_compact(something)
Eu sei que esta discussão é um pouco antiga, mas eu vim com uma solução melhor que suporta hashes multidimensionais. Ele usa delete_if? exceto sua multidimensional e limpa qualquer coisa com um valor vazio por padrão e se um bloco é passado, ele é passado através de seus filhos.
# 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
Eu criei um método deep_compact para isso que filtra recursivamente os registros nulos (e, opcionalmente, os registros em branco):
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!
e Hash#delete_if!
não funcionam em nested nil
, empty?
e / ou blank?
valores. Note-se que os dois últimos métodos são destrutivos, e que todas nil
, ""
, false
, []
e {}
os valores são contados quantoblank?
.
Hash#compact
e Hash#compact!
estão disponíveis apenas no Rails ou Ruby versão 2.4.0 e superior.
Aqui está uma solução não destrutiva que remove todas as matrizes, hashes, strings e nil
valores vazios , mantendo todos os false
valores:
( blank?
pode ser substituído por nil?
ou empty?
conforme necessário.)
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
Uma versão destrutiva:
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
Ou, se você deseja adicionar ambas as versões como métodos de instância na Hash
classe:
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
Outras opções:
v.blank? && v != false
por v.nil? || v == ""
para remover estritamente as cordas vazias enil
valoresv.blank? && v != false
por v.nil?
para remover estritamente os nil
valoresEDITADO 2017/03/15 para manter false
valores e apresentar outras opções
Em Um liner simples para excluir valores nulos no Hash,
rec_hash.each {|key,value| rec_hash.delete(key) if value.blank? }
blank?
vai para cadeias vazias bem
Pode ser feito com a biblioteca de facetas (faltam recursos da biblioteca padrão), assim:
require 'hash/compact'
require 'enumerable/recursively'
hash.recursively { |v| v.compact! }
Funciona com qualquer enumerável (incluindo matriz, hash).
Veja como o método recursivamente é implementado.
Eu acredito que seria melhor usar um método auto recursivo. Dessa forma, vai tão fundo quanto é necessário. Isso excluirá o par de valores-chave se o valor for nulo ou um Hash vazio.
class Hash
def compact
delete_if {|k,v| v.is_a?(Hash) ? v.compact.empty? : v.nil? }
end
end
Em seguida, usá-lo ficará assim:
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}}
Para manter hashes vazios, você pode simplificar isso para.
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
Tente fazer isso para remover nada
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!
A versão recursiva de https://stackoverflow.com/a/14773555/1519240 funciona, mas não com HashWithIndifferentAccess
ou outras classes que são do tipo Hash ..
Aqui está a versão que estou usando:
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)
aceitará mais classes que são como um Hash.
Você também pode substituir inject({})
por inject(HashWithIndifferentAccess.new)
se desejar acessar o novo hash usando o símbolo e a sequência.
Aqui está algo que eu tenho:
# 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
Exclusão profunda de zero valores de um hash.
# 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
Se você estiver usando Rails
(ou autônomo ActiveSupport
), a partir da versão 6.1
, existe um compact_blank
método que remove os blank
valores dos hashes.
Ele é usado Object#blank?
sob o capô para determinar se um item está em branco.
{ a: "", b: 1, c: nil, d: [], e: false, f: true }.compact_blank
# => { b: 1, f: true }
Aqui está um link para os documentos e um link para o PR relativo .
Uma variante destrutiva também está disponível. Veja Hash#compact_blank!
.
Se você precisar remover apenas nil
valores,
por favor, considere o uso de métodos Hash#compact
e build de Ruby Hash#compact!
.
{ a: 1, b: false, c: nil }.compact
# => { a: 1, b: false }