Alle leeren Elemente aus einem Hash / YAML entfernen?

133

Wie würde ich vorgehen, um alle leeren Elemente (leere Listenelemente) aus einer verschachtelten Hash- oder YAML-Datei zu entfernen?

Brian Jordan
quelle

Antworten:

70

Sie können Hash wie folgt eine kompakte Methode hinzufügen

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

oder für eine Version, die Rekursion unterstützt

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
quelle
2
Kompakt sollte nur Nullen entfernen. Keine falschen Werte
Ismael Abreu
1
Dies hat ein Problem: Es Hash#delete_ifhandelt sich um eine destruktive Operation, während compactMethoden das Objekt nicht ändern. Sie können verwenden Hash#reject. Oder rufen Sie die Methode auf Hash#compact!.
Tokand
5
Bitte beachten Sie dies compactund compact!kommen standardmäßig in Ruby => 2.4.0 und Rails => 4.1. Sie sind jedoch nicht rekursiv.
Aidan
Die rekursive Version funktioniert nicht mit HashWithIndifferentAccess. Überprüfen Sie meine Version unter stackoverflow.com/a/53958201/1519240
user1519240
156

Rails 4.1 hat Hash # compact und Hash # compact hinzugefügt ! als Kernerweiterung von Rubys HashKlasse. Sie können sie folgendermaßen verwenden:

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: Diese Implementierung ist nicht rekursiv. Aus Neugier haben sie es #selectanstelle von #delete_ifaus Leistungsgründen implementiert . Siehe hier für den Benchmark .

Falls Sie es auf Ihre Rails 3-App zurückportieren möchten:

# 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
quelle
3
Schön und ordentlich, aber wahrscheinlich erwähnenswert, dass die Rails-Erweiterung im Gegensatz zur akzeptierten Antwort nicht rekursiv ist?
SirRawlins
2
Leere Hashes werden weggelassen.
Sebastian Palma
142

Verwenden Sie hsh.delete_if . In Ihrem speziellen Fall so etwas wie:hsh.delete_if { |k, v| v.empty? }

jpemberthy
quelle
6
Rekursiv:proc = Proc.new { |k, v| v.kind_of?(Hash) ? (v.delete_if(&l); nil) : v.empty? }; hsh.delete_if(&proc)
Daniel O'Hara
3
Ich glaube, Ihre ansonsten korrekte Antwort enthält einen Tippfehler: proc = Proc.new {| k, v | v.kind_of? (Hash)? (v.delete_if (& proc); nil): v.leer? }; hsh.delete_if (& proc)
acw
3
@Beven es scheint, sie haben dich gehört! api.rubyonrails.org/classes/Hash.html#method-i-compact (Rails 4.1)
dgilperez
2
Dies wirft ein NoMethodErrorWenn vist Null.
Jerrod
6
Sie können .delete_if {| k, v | verwenden v.blank? }
Serhii Nadolynskyi
7

Dieser würde auch leere Hashes löschen:

swoop = Proc.new { |k, v| v.delete_if(&swoop) if v.kind_of?(Hash);  v.empty? }
hsh.delete_if &swoop
punund
quelle
1
Rails-Version, die auch mit Werten anderer Typen als Array, Hash oder String (wie Fixnum) funktioniert:swoop = Proc.new { |k, v| v.delete_if(&swoop) if v.kind_of?(Hash); v.blank? }
wdspkr
6

Mit Hash # ablehnen können Sie leere Schlüssel / Wert-Paare aus einem Ruby-Hash entfernen.

# 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
quelle
4
Zu .empty?Ihrer Information: Wirft Fehler für Zahlen, so dass Sie .blank?inRails
illusionist
5

funktioniert sowohl für Hashes als auch für 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

PS basierend auf jemandes Antwort, kann nicht finden

Verwendung - Helpers::RecursiveCompact.recursive_compact(something)

Srghma
quelle
4

Ich weiß, dass dieser Thread etwas alt ist, aber ich habe eine bessere Lösung gefunden, die mehrdimensionale Hashes unterstützt. Es verwendet delete_if? außer es ist mehrdimensional und bereinigt standardmäßig alles mit einem leeren Wert. Wenn ein Block übergeben wird, wird er über seine untergeordneten Elemente weitergegeben.

# 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
quelle
4

Ich habe dafür eine deep_compact-Methode erstellt, die rekursiv keine Datensätze herausfiltert (und optional auch leere Datensätze):

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
quelle
4

Ruby Hash#compact, Hash#compact!und Hash#delete_if!nicht die Arbeit an verschachtelten nil, empty?und / oder blank?Werte. Man beachte , dass die beiden letztgenannten Methoden destruktiv sind, und dass alle nil, "", false, []und {}Werte werden gezählt alsblank? .

Hash#compactund Hash#compact!sind nur in Rails oder Ruby Version 2.4.0 und höher verfügbar.

Hier ist eine zerstörungsfreie Lösung, die alle leeren Arrays, Hashes, Strings und nilWerte entfernt und dabei alle falseWerte beibehält:

( blank?kann durch nil?oder empty?nach Bedarf ersetzt werden.)

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

Eine destruktive Version:

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

Oder wenn Sie beide Versionen als Instanzmethoden zur HashKlasse hinzufügen möchten :

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

Andere Optionen:

  • Ersetzen Sie v.blank? && v != falsedurch v.nil? || v == "", um leere Zeichenfolgen und strikt zu entfernennil Werte
  • Ersetzen v.blank? && v != falsedurch v.nil?, um nilWerte strikt zu entfernen
  • Etc.

EDITED 2017/03/15, um falseWerte zu halten und andere Optionen zu präsentieren

Sebastian Jay
quelle
3

Unsere Version: Es werden auch die leeren Zeichenfolgen und Nullwerte bereinigt

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
quelle
3

In Simple One Liner zum Löschen von Nullwerten in Hash,

rec_hash.each {|key,value| rec_hash.delete(key) if value.blank? } 
Ramya
quelle
Vorsicht, blank?geht auch für leere Saiten
Hertzel Guinness
2

Könnte mit Facettenbibliothek (eine fehlende Funktion aus der Standardbibliothek) wie folgt gemacht werden:

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

Funktioniert mit allen Enumerable (einschließlich Array, Hash).

Schauen Sie, wie die rekursive Methode implementiert ist.

Dmitry Polushkin
quelle
0

Ich glaube, es wäre am besten, eine selbstrekursive Methode zu verwenden. Auf diese Weise geht es so tief wie nötig. Dadurch wird das Schlüsselwertpaar gelöscht, wenn der Wert Null oder ein leerer Hash ist.

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

Dann sieht es folgendermaßen aus:

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

Um leere Hashes zu behalten, können Sie dies vereinfachen.

class Hash
  def compact
    delete_if {|k,v| v.compact if v.is_a?(Hash); v.nil? }
  end
end
6ft Dan
quelle
hmm. Zirkelverweise könnten zu einer Endlosschleife IIUC führen.
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
quelle
Beachten Sie, dass "wenn Hash dann kompakt (val) .empty?" sollte sein "wenn Hash dann val.compact.empty?"
AlexITC
0

Versuchen Sie dies, um Null zu entfernen

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
quelle
oder einfachhash.compact!
Courtsimas
0

Die rekursive Version von https://stackoverflow.com/a/14773555/1519240 funktioniert, jedoch nicht mitHashWithIndifferentAccess oder anderen Klassen, die eine Art Hash sind.

Hier ist die Version, die ich benutze:

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) akzeptiert mehr Klassen, die wie ein Hash sind.

Sie können auch ersetzen inject({})durch , inject(HashWithIndifferentAccess.new)wenn Sie den neuen Hash unter Verwendung von sowohl Symbol und String zugreifen möchten.

user1519240
quelle
0

Hier ist etwas, was ich habe:

# 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
quelle
0

Tiefes Löschen von Nullwerten aus einem 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
quelle
0

Wenn Sie ab Version Rails(oder Standalone ActiveSupport) verwenden, 6.1gibt es eine compact_blankMethode, mit der blankWerte aus Hashes entfernt werden.

Es wird Object#blank?unter der Haube verwendet, um festzustellen, ob ein Artikel leer ist.

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

Hier ist ein Link zu den Dokumenten und ein Link zur relativen PR .

Eine destruktive Variante ist ebenfalls erhältlich. Siehe Hash#compact_blank!.


Wenn Sie nur nilWerte entfernen müssen ,

Bitte erwägen Sie die Verwendung von Ruby Build-In Hash#compactund Hash#compact!Methoden.

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