¿Eliminar todos los elementos vacíos de un hash / YAML?

133

¿Cómo haría para eliminar todos los elementos vacíos (elementos de lista vacíos) de un archivo Hash o YAML anidado?

Brian Jordan
fuente

Respuestas:

70

Podría agregar un método compacto a Hash como este

class Hash
  def compact
    delete_if { |k, v| v.nil? }
  end
end

o para una versión que admite recursividad

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
opsb
fuente
2
compact solo debe eliminar los nils. No valores falsos
Ismael Abreu
1
Esto tiene un problema: Hash#delete_ifes una operación destructiva, mientras que los compactmétodos no modifican el objeto. Puedes usar Hash#reject. O llame al método Hash#compact!.
tokland el
55
Tenga en cuenta que compacty compact!viene estándar en Ruby => 2.4.0, y Rails => 4.1. Sin embargo, no son recursivos.
aidan
La versión recursiva no funciona con HashWithIndifferentAccess... Verifique mi versión en stackoverflow.com/a/53958201/1519240
user1519240
157

¡Rails 4.1 agregó Hash # compact y Hash # compact! como extensiones centrales de la Hashclase de Ruby . Puedes usarlos así:

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                  
# => {}

Atención: esta implementación no es recursiva. Como curiosidad, lo implementaron usando en #selectlugar de #delete_ifpor razones de rendimiento. Ver aquí para el punto de referencia .

En caso de que desee realizar un backport a su aplicación 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
dgilperez
fuente
3
Agradable y ordenado, pero probablemente valga la pena señalar que, a diferencia de la respuesta aceptada, la extensión Rails no es recursiva.
SirRawlins
2
Omite hashes vacíos.
Sebastian Palma
142

Use hsh.delete_if . En su caso específico, algo como:hsh.delete_if { |k, v| v.empty? }

jpemberthy
fuente
66
Uno recursivo:proc = Proc.new { |k, v| v.kind_of?(Hash) ? (v.delete_if(&l); nil) : v.empty? }; hsh.delete_if(&proc)
Daniel O'Hara
3
Creo que hay un error tipográfico en su respuesta correcta: proc = Proc.new {| k, v | v.kind_of? (Hash)? (v.delete_if (& proc); nil): v.empty? }; hsh.delete_if (& proc)
acw
3
@BSeven parece que te escucharon! api.rubyonrails.org/classes/Hash.html#method-i-compact (Rails 4.1)
dgilperez
2
Esto arrojará un NoMethodErrorif si ves nulo.
Jerrod
66
Puede usar .delete_if {| k, v | v.blank? }
Serhii Nadolynskyi
7

Este también eliminaría los hashes vacíos:

swoop = Proc.new { |k, v| v.delete_if(&swoop) if v.kind_of?(Hash);  v.empty? }
hsh.delete_if &swoop
punund
fuente
1
versión de rails, que también funciona con valores de otros tipos que no sean Array, Hash o String (como Fixnum):swoop = Proc.new { |k, v| v.delete_if(&swoop) if v.kind_of?(Hash); v.blank? }
wdspkr
6

Puede usar Hash # rechazar para eliminar pares de clave / valor vacíos de un ruby ​​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"}
smd1000
fuente
44
FYI: .empty?arroja un error para los números, por lo que puede usar .blank?enRails
ilusionista el
5

funciona tanto para hashes como para arrays

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

PD basado en la respuesta de alguien, no puedo encontrar

uso - Helpers::RecursiveCompact.recursive_compact(something)

srghma
fuente
4

Sé que este hilo es un poco viejo, pero se me ocurrió una solución mejor que admite hashes multidimensionales. Utiliza delete_if? excepto que es multidimensional y limpia cualquier cosa con un valor vacío de forma predeterminada y si se pasa un bloque, se pasa a través de sus hijos.

# 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
Kelly Becker
fuente
4

Hice un método deep_compact para esto que filtra recursivamente registros nulos (y opcionalmente, registros en blanco también):

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
mwalsher
fuente
4

Ruby Hash#compact, Hash#compact!y Hash#delete_if!no funcionan en anidados nil, empty?y / o blank?valores. Tenga en cuenta que los dos últimos métodos son destructivos, y que todos nil, "", false, []y {}los valores se cuentan como blank?.

Hash#compacty Hash#compact!solo están disponibles en Rails, o Ruby versión 2.4.0 y superior.

Aquí hay una solución no destructiva que elimina todas las matrices, hashes, cadenas y nilvalores vacíos , manteniendo todos los falsevalores:

( blank?se puede reemplazar con nil?o empty?según sea necesario)

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

Una versión destructiva:

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

O, si desea agregar ambas versiones como métodos de instancia en la Hashclase:

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

Otras opciones:

  • Reemplace v.blank? && v != falsecon v.nil? || v == ""para eliminar estrictamente cadenas y nilvalores vacíos
  • Reemplace v.blank? && v != falsecon v.nil?para eliminar estrictamente los nilvalores
  • Etc.

EDITADO el 15/03/2017 para mantener falsevalores y presentar otras opciones

Sebastian Jay
fuente
3

nuestra versión: también limpia las cadenas vacías y los valores nulos

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
sahin
fuente
3

En Simple one liner para eliminar valores nulos en Hash,

rec_hash.each {|key,value| rec_hash.delete(key) if value.blank? } 
ramya
fuente
cuidado, blank?va por cuerdas vacías también
Hertzel Guinness
2

Se podría hacer con la biblioteca de facetas (características que faltan de la biblioteca estándar), así:

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

Funciona con cualquier Enumerable (incluyendo Array, Hash).

Mira cómo se implementa el método recursivamente .

Dmitry Polushkin
fuente
0

Creo que sería mejor usar un método auto recursivo. De esa manera va tan profundo como sea necesario. Esto eliminará el par de valores clave si el valor es nulo o un hash vacío.

class Hash
  def compact
    delete_if {|k,v| v.is_a?(Hash) ? v.compact.empty? : v.nil? }
  end
end

Luego, usarlo se verá así:

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 mantener hashes vacíos, puede simplificar esto.

class Hash
  def compact
    delete_if {|k,v| v.compact if v.is_a?(Hash); v.nil? }
  end
end
6 pies Dan
fuente
hmm referencias circulares podrían conducir a un bucle infinito IIUC.
Hertzel Guinness
0
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
Chix
fuente
Tenga en cuenta que "cuando Hash luego compacto (val) .empty?" debería ser "cuando Hash entonces val.compact.empty?"
AlexITC
0

Intenta esto para eliminar 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}
Rahul Patel
fuente
o simplementehash.compact!
courtimas
0

La versión recursiva de https://stackoverflow.com/a/14773555/1519240 funciona, pero no con HashWithIndifferentAccessu otras clases que son una especie de Hash.

Aquí está la versión que estoy 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) aceptará más clases que son como un Hash.

También puede reemplazar inject({})por inject(HashWithIndifferentAccess.new)si desea acceder al nuevo hash utilizando tanto el símbolo como la cadena.

usuario1519240
fuente
0

Aquí hay algo que tengo:

# 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
Varun Garg
fuente
0

Eliminación profunda de valores nulos de un 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
Miro Mudrik
fuente
0

Si está utilizando Rails(o de forma independiente ActiveSupport), a partir de la versión 6.1, hay un compact_blankmétodo que elimina los blankvalores de los hashes.

Se utiliza Object#blank?debajo del capó para determinar si un elemento está en blanco.

{ a: "", b: 1, c: nil, d: [], e: false, f: true }.compact_blank
# => { b: 1, f: true }

Aquí hay un enlace a los documentos y un enlace al PR relativo .

Una variante destructiva también está disponible. Ver Hash#compact_blank!.


Si necesita eliminar solo nilvalores,

por favor, considere usar los métodos Hash#compacty la compilación de Ruby Hash#compact!.

{ a: 1, b: false, c: nil }.compact
# => { a: 1, b: false }
Marian13
fuente