Supprimer tous les éléments vides d'un hachage / YAML?


133

Comment procéder pour supprimer tous les éléments vides (éléments de liste vides) d'un fichier Hash ou YAML imbriqué?

Réponses:


70

Vous pouvez ajouter une méthode compacte à Hash comme ceci

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

ou pour une version prenant en charge la récursivité

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 ne doit supprimer que les nils. Pas de fausses valeurs
Ismael Abreu

1
Cela pose un problème: Hash#delete_ifc'est une opération destructive, alors que les compactméthodes ne modifient pas l'objet. Vous pouvez utiliser Hash#reject. Ou appelez la méthode Hash#compact!.
tokland

5
Veuillez noter que compactet compact!venir en standard dans Ruby => 2.4.0, et Rails => 4.1. Ils ne sont cependant pas récursifs.
aidan

La version récursive ne fonctionne pas avec HashWithIndifferentAccess.. Vérifiez ma version sur stackoverflow.com/a/53958201/1519240
user1519240

157

Rails 4.1 a ajouté Hash # compact et Hash # compact! comme extensions de base de la Hashclasse Ruby . Vous pouvez les utiliser comme ceci:

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

Attention: cette implémentation n'est pas récursive. Par curiosité, ils l'ont implémenté en utilisant #selectplutôt que #delete_ifpour des raisons de performances. Voir ici pour le benchmark .

Si vous souhaitez le rétroporter sur votre application 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

3
Nice et bien rangé, mais il vaut probablement la peine de noter que contrairement à la réponse acceptée, l'extension Rails n'est pas récursive?
SirRawlins

2
Il omet les hachages vides.
Sebastian Palma

142

Utilisez hsh.delete_if . Dans votre cas spécifique, quelque chose comme:hsh.delete_if { |k, v| v.empty? }


6
Récursif:proc = Proc.new { |k, v| v.kind_of?(Hash) ? (v.delete_if(&l); nil) : v.empty? }; hsh.delete_if(&proc)
Daniel O'Hara

3
Je crois qu'il y a une faute de frappe dans votre réponse par ailleurs correcte: proc = Proc.new {| k, v | v.kind_of? (Hash)? (v.delete_if (& proc); nil): v.empty? }; hsh.delete_if (& proc)
acw

3
@BSeven il semble qu'ils vous ont entendu! api.rubyonrails.org/classes/Hash.html#method-i-compact (Rails 4.1)
dgilperez

2
Cela lancera un NoMethodErrorsi vest nul.
Jerrod

6
Vous pouvez utiliser .delete_if {| k, v | v.blank? }
Serhii Nadolynskyi


7

Celui-ci supprimerait également les hachages vides:

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

1
rails, qui fonctionne également avec des valeurs d'autres types que Array, Hash ou String (comme Fixnum):swoop = Proc.new { |k, v| v.delete_if(&swoop) if v.kind_of?(Hash); v.blank? }
wdspkr

6

Vous pouvez utiliser Hash # Rejeter pour supprimer des paires clé / valeur vides d'un 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"}

4
FYI: .empty?jette une erreur pour les nombres, vous pouvez donc utiliser .blank?dansRails
illusionniste

5

fonctionne à la fois pour les hachages et les tableaux

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 basé sur la réponse de quelqu'un, impossible de trouver

utilisation - Helpers::RecursiveCompact.recursive_compact(something)


4

Je sais que ce fil est un peu vieux mais j'ai trouvé une meilleure solution qui prend en charge les hachages multidimensionnels. Il utilise delete_if? sauf qu'il est multidimensionnel et nettoie tout ce qui a une valeur vide par défaut et si un bloc est passé, il est passé à travers ses enfants.

# 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

4

J'ai créé une méthode deep_compact pour cela qui filtre récursivement les enregistrements nuls (et éventuellement, les enregistrements vides également):

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

4

Ruby Hash#compact, Hash#compact!et Hash#delete_if!ne fonctionnent pas sur imbriquée nil, empty?et / ou des blank?valeurs. Notez que les deux dernières méthodes sont destructrices, et que tous nil, "", false, []et les {}valeurs sont considérées commeblank? .

Hash#compactet Hash#compact!ne sont disponibles que dans Rails ou Ruby version 2.4.0 et supérieure.

Voici une solution non destructive qui supprime tous les tableaux vides, les hachages, les chaînes et les nilvaleurs, tout en conservant toutes les falsevaleurs:

( blank?peut être remplacé par nil?ou empty?au besoin.)

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

Une version destructrice:

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, si vous souhaitez ajouter les deux versions en tant que méthodes d'instance sur la 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

Autres options:

  • Remplacer v.blank? && v != falsepar v.nil? || v == ""pour supprimer strictement les chaînes vides etnil valeurs
  • Remplacer v.blank? && v != falsepar v.nil?pour supprimer strictement les nilvaleurs
  • Etc.

EDITED 2017/03/15 pour conserver les falsevaleurs et présenter d'autres options


3

notre version: elle nettoie également les chaînes vides et les valeurs nulles

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

3

Dans Simple one liner pour supprimer les valeurs nulles dans Hash,

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

attention, blank?va aussi pour les cordes vides
Hertzel Guinness

2

Peut être fait avec la bibliothèque de facettes (une fonctionnalité manquante de la bibliothèque standard), comme ça:

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

Fonctionne avec n'importe quel Enumerable (y compris Array, Hash).

Regardez comment la méthode est implémentée de manière récursive .


0

Je pense qu'il serait préférable d'utiliser une méthode auto-récursive. De cette façon, il va aussi loin que nécessaire. Cela supprimera la paire clé / valeur si la valeur est nulle ou un hachage vide.

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

Ensuite, l'utiliser ressemblera à ceci:

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

Pour conserver les hachages vides, vous pouvez simplifier cela en.

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

hmm. les références circulaires pourraient conduire à une boucle infinie 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

Notez que "quand Hash puis compact (val) .empty?" devrait être "quand Hash puis val.compact.empty?"
AlexITC

0

Essayez ceci pour supprimer nil

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}

ou simplementhash.compact!
courtsimas

0

La version récursive de https://stackoverflow.com/a/14773555/1519240 fonctionne, mais pas avec HashWithIndifferentAccessou avec d' autres classes qui sont en quelque sorte Hash.

Voici la version que j'utilise:

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) acceptera plus de classes qui ressemblent à un hachage.

Vous pouvez également remplacer inject({})par inject(HashWithIndifferentAccess.new)si vous souhaitez accéder au nouveau hachage en utilisant à la fois le symbole et la chaîne.


0

Voici quelque chose que j'ai:

# 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

0

Suppression profonde des valeurs nulles d'un hachage.

  # 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

0

Si vous utilisez Rails(ou une version autonome ActiveSupport), à partir de la version 6.1, il existe une compact_blankméthode qui supprime les blankvaleurs des hachages.

Il utilise Object#blank?sous le capot pour déterminer si un élément est vide.

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

Voici un lien vers les documents et un lien vers le PR relatif .

Une variante destructive est également disponible. Voir Hash#compact_blank!.


Si vous devez supprimer uniquement des nilvaleurs,

s'il vous plaît, pensez à utiliser l'intégration Hash#compactet les Hash#compact!méthodes Ruby .

{ a: 1, b: false, c: nil }.compact
# => { a: 1, b: false }
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.