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 compactmétodos não modificam o objeto. Você pode usar Hash#reject. Ou chamar o método Hash#compact!.
compacte 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 Hashclasse 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 #selectvez de #delete_ifpor 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)
NoMethodErrorif vé nulo.
Se você usa o Ruby 2.4+, pode ligar compactecompact!
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#compacte 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 nilvalores vazios , mantendo todos os falsevalores:
( 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 Hashclasse:
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 != falsepor v.nil? || v == ""para remover estritamente as cordas vazias enil valoresv.blank? && v != falsepor v.nil?para remover estritamente os nilvaloresEDITADO 2017/03/15 para manter falsevalores 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 HashWithIndifferentAccessou 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_blankmétodo que remove os blankvalores 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 nilvalores,
por favor, considere o uso de métodos Hash#compacte build de Ruby Hash#compact!.
{ a: 1, b: false, c: nil }.compact
# => { a: 1, b: false }