Wie entferne ich alle leeren Elemente (leere Listenelemente) aus einer verschachtelten Hash- oder YAML-Datei?
Sie könnten Hash so 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
Verwenden Sie hsh.delete_if . In Ihrem speziellen Fall etwa Folgendes: hsh.delete_if { |k, v| v.empty? }
Rails 4.1 hinzugefügt Hash # compact und Hash # compact! als Kernerweiterung der Hash
-Klasse von Ruby. Sie können sie wie folgt 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. Als Kuriosität wurde aus Performancegründen #select
anstelle von #delete_if
implementiert. Sehen Sie hier für die Benchmark .
Falls Sie es in 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
Dieser würde auch leere Hash löschen:
swoop = Proc.new { |k, v| v.delete_if(&swoop) if v.kind_of?(Hash); v.empty? }
hsh.delete_if &swoop
Mit Hash # reject 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"}
Ich weiß, dass dieser Thread ein bisschen alt ist, aber ich habe eine bessere Lösung gefunden, die mehrdimensionale Hashes unterstützt. Es verwendet delete_if? mit Ausnahme des mehrdimensionalen Bereichs und löscht alle Elemente mit einem leeren Wert. Wenn ein Block übergeben wird, wird er durch 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
Ich habe dafür eine deep_compact-Methode erstellt, die rekursiv keine Datensätze (und optional auch leere Datensätze) herausfiltert:
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
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
P.S. basierend auf jemandes Antwort, kann nicht finden
In Simple one Liner zum Löschen von Nullwerten in Hash
rec_hash.each {|key,value| rec_hash.delete(key) if value.blank? }
Rubys Hash#compact
, Hash#compact!
und Hash#delete_if!
funktionieren nicht mit verschachtelten nil
-, empty?
- und/oder blank?
-Werten. Beachten Sie, dass die beiden letztgenannten Methoden destruktiv sind und dass alle nil
, ""
, false
, []
und {}
-Werte als blank?
gezählt werden.
Hash#compact
und 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 nil
-Werte entfernt und dabei alle false
-Werte beibehält:
(blank?
kann bei Bedarf durch nil?
oder empty?
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 für die Klasse Hash
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:
v.blank? && v != false
durch v.nil? || v == ""
, um leere Zeichenfolgen und nil
-Werte strikt zu entfernenv.blank? && v != false
durch v.nil?
, um nil
-Werte strikt zu entfernenBEARBEITET 2017/03/15, um false
Werte beizubehalten und andere Optionen darzustellen
in unserer Version: werden auch die leeren Zeichenfolgen und Nullwerte gelöscht
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
Könnte mit facets library (fehlenden Funktionen aus der Standardbibliothek) wie folgt gemacht werden:
require 'hash/compact'
require 'enumerable/recursively'
hash.recursively { |v| v.compact! }
Funktioniert mit jedem Enumerable (einschließlich Array, Hash).
Schauen Sie, wie rekursiv Methode implementiert wird.
Die rekursive Version von https://stackoverflow.com/a/14773555/1519240 funktioniert, jedoch nicht mit HashWithIndifferentAccess
oder anderen Klassen, die eine Art Hash sind.
Hier ist die Version, die ich verwende:
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 einem Hash ähneln.
Sie können inject({})
auch durch inject(HashWithIndifferentAccess.new)
ersetzen, wenn Sie mit Symbol und String auf den neuen Hash zugreifen möchten.
Folgendes habe ich:
# 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
Versuchen Sie dies, um nichts 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}
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 wird es so aussehen:
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
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