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é?

Brian Jordan
la source

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
opsb
la source
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
dgilperez
la source
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? }

jpemberthy
la source
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
punund
la source
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"}
smd1000
la source
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)

srghma
la source
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
Kelly Becker
la source
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
mwalsher
la source
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

Sebastian Jay
la source
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
sahin
la source
3

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

rec_hash.each {|key,value| rec_hash.delete(key) if value.blank? } 
ramya
la source
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 .

Dmitry Polushkin
la source
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
6 pieds Dan
la source
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
Chix
la source
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}
Rahul Patel
la source
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.

utilisateur1519240
la source
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
Varun Garg
la source
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
Miro Mudrik
la source
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 }
Marian13
la source