2010-08-10 10 views

Respuesta

49

Se podría añadir un método compacto en Hash como esto

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

o para una versión que soporta la 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 
+2

compact solo debe eliminar nils. No falsy valores –

+1

un muy buen punto! Actualicé solo valores compactos nulos. – opsb

+1

Esto tiene un problema: 'Hash # delete_if' es una operación destructiva, mientras que los métodos' compact' no modifican el objeto. Puede usar 'Hash # reject'. O llame al método 'Hash # compact!'. – tokland

125

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

+6

recursiva uno: 'proc = Proc.new {| k, v | v.kind_of? (Hash)? (v.delete_if (&l); nil): v.empty?}; hsh.delete_if (& proc) ' –

+3

Creo que hay un error ortográ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

+0

Sería genial si se añadiera #compact a Hash. –

6

Éste sería eliminar los hashes vacías también:

swoop = Proc.new { |k, v| v.delete_if(&swoop) if v.kind_of?(Hash); v.empty? } 
hsh.delete_if &swoop 
+0

versión de rieles, que también funciona con valores de otros tipos distintos de Array, Hash o String (como Fixnum): 'swoop = Proc.new {| k, v | v.delete_if (& swoop) si v.kind_of? (Hash); v.blank? } ' – wdspkr

5

Sé que este hilo es un poco viejo, pero se me ocurrió una mejor solución que soporta los hashes multidimensionales. Utiliza delete_if? excepto que es multidimensional y borra todo lo que tiene un valor vacío por defecto 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 
2

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

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 
108

Rails 4.1 agregó Hash#compact y Hash#compact! como extensiones principales a la clase Hash de Ruby. Puede 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     
# => {} 

Heads up: esta implementación no es recursiva. Como curiosidad, lo implementaron usando #select en lugar de #delete_if por razones de rendimiento. Ver here for the benchmark.

En caso de que acondicionarlo a sus carriles 3 aplicación:

# 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 
+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

En Simple de un forro para borrar los valores nulos en Hash,

rec_hash.each {|key,value| rec_hash.delete(key) if value.blank? } 
+0

cuidado, 'en blanco?' Vale para cadenas vacías también –

0

Creo que sería mejor usar un método auto recursivo De esa forma va tan profundo como se necesita. 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, utilizando va a tener este aspecto:

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 los hashes vacíos podrá simplificar esto.

class Hash 
    def compact 
    delete_if {|k,v| v.compact if v.is_a?(Hash); v.nil? } 
    end 
end 
+0

hmm. referencias circulares podrían conducir a un bucle infinito IIUC. –

4

Hice un método deep_compact para este que filtra recursivamente a cabo nil registros (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 
5

Puede utilizar Hash#reject eliminar los pares clave/valor vacías de una 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"} 
+2

FYI: '.empty?' Arroja un error para los números, por lo que puede usar '.blank?' En 'Rails' – illusionist

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 
+0

Tenga en cuenta que" cuando Hash then compact (val) .empty? " debería ser "cuando Hash luego val.compact.empty?" – AlexITC

+0

@vhax fijo !!!! – Chix

2

se podría hacer con facets biblioteca (una serie de 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).

Mire cómo se implementa recursively method.

2

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

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

Aquí es una solución no destructiva que elimina todas las matrices vacías, hashes, cuerdas y nil valores, manteniendo todas las false valores: (. blank? se pueden sustituir por 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 bien, si desea agregar las dos versiones como métodos de instancia de la clase 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 

Otras opciones:

  • Reemplazar v.blank? && v != false con v.nil? || v == "" para eliminar estrictamente cadenas vacías y nil valores
  • Reemplazar v.blank? && v != false con v.nil? para eliminar estrictamente nil valores
  • Etc.

EDITADO 15/03/2017 mantener false valores y presentes otras opciones

0

probar este para eliminar nula

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} 
Cuestiones relacionadas