Ich habe einen Hash:
h1 = {:a => :A, :b => :B, :c => :C, :d => :D}
Was ist der beste Weg, um einen solchen Sub-Hash zu extrahieren?
h1.extract_subhash(:b, :d, :e, :f) # => {:b => :B, :d => :D}
h1 #=> {:a => :A, :c => :C}
Ich habe einen Hash:
h1 = {:a => :A, :b => :B, :c => :C, :d => :D}
Was ist der beste Weg, um einen solchen Sub-Hash zu extrahieren?
h1.extract_subhash(:b, :d, :e, :f) # => {:b => :B, :d => :D}
h1 #=> {:a => :A, :c => :C}
Antworten:
Wenn Sie speziell möchten, dass die Methode die extrahierten Elemente zurückgibt, h1 jedoch gleich bleibt:
h1 = {:a => :A, :b => :B, :c => :C, :d => :D}
h2 = h1.select {|key, value| [:b, :d, :e, :f].include?(key) } # => {:b=>:B, :d=>:D}
h1 = Hash[h1.to_a - h2.to_a] # => {:a=>:A, :c=>:C}
Und wenn Sie das in die Hash-Klasse patchen möchten:
class Hash
def extract_subhash(*extract)
h2 = self.select{|key, value| extract.include?(key) }
self.delete_if {|key, value| extract.include?(key) }
h2
end
end
Wenn Sie nur die angegebenen Elemente aus dem Hash entfernen möchten, ist dies mit delete_if viel einfacher .
h1 = {:a => :A, :b => :B, :c => :C, :d => :D}
h1.delete_if {|key, value| [:b, :d, :e, :f].include?(key) } # => {:a=>:A, :c=>:C}
h1 # => {:a=>:A, :c=>:C}
slice
oder except
, abhängig von Ihren Bedürfnissen) viel sauberer
ActiveSupport
Zumindest seit 2.3.8 stellt vier praktische Möglichkeiten: #slice
, #except
sowie deren Pendants destruktive: #slice!
und #except!
. Sie wurden in anderen Antworten erwähnt, aber um sie an einer Stelle zusammenzufassen:
x = {a: 1, b: 2, c: 3, d: 4}
# => {:a=>1, :b=>2, :c=>3, :d=>4}
x.slice(:a, :b)
# => {:a=>1, :b=>2}
x
# => {:a=>1, :b=>2, :c=>3, :d=>4}
x.except(:a, :b)
# => {:c=>3, :d=>4}
x
# => {:a=>1, :b=>2, :c=>3, :d=>4}
Beachten Sie die Rückgabewerte der Bang-Methoden. Sie passen nicht nur vorhandenen Hash an, sondern geben auch entfernte (nicht beibehaltene) Einträge zurück. Das Hash#except!
passt am besten zum Beispiel in der Frage:
x = {a: 1, b: 2, c: 3, d: 4}
# => {:a=>1, :b=>2, :c=>3, :d=>4}
x.except!(:c, :d)
# => {:a=>1, :b=>2}
x
# => {:a=>1, :b=>2}
ActiveSupport
benötigt keine ganzen Schienen, ist ziemlich leicht. Tatsächlich hängen viele Edelsteine, die keine Schienen sind, davon ab, sodass Sie sie höchstwahrscheinlich bereits in Gemfile.lock haben. Sie müssen die Hash-Klasse nicht selbst erweitern.
x.except!(:c, :d)
(mit Knall) sollte sein # => {:a=>1, :b=>2}
. Gut, wenn Sie Ihre Antwort bearbeiten können.
Wenn Sie Schienen verwenden , ist Hash # Slice der richtige Weg.
{:a => :A, :b => :B, :c => :C, :d => :D}.slice(:a, :c)
# => {:a => :A, :c => :C}
Wenn Sie keine Schienen verwenden , gibt Hash # values_at die Werte in derselben Reihenfolge zurück, in der Sie sie gefragt haben, damit Sie Folgendes tun können:
def slice(hash, *keys)
Hash[ [keys, hash.values_at(*keys)].transpose]
end
def except(hash, *keys)
desired_keys = hash.keys - keys
Hash[ [desired_keys, hash.values_at(*desired_keys)].transpose]
end
Ex:
slice({foo: 'bar', 'bar' => 'foo', 2 => 'two'}, 'bar', 2)
# => {'bar' => 'foo', 2 => 'two'}
except({foo: 'bar', 'bar' => 'foo', 2 => 'two'}, 'bar', 2)
# => {:foo => 'bar'}
Erläuterung:
Raus {:a => 1, :b => 2, :c => 3}
wollen wir{:a => 1, :b => 2}
hash = {:a => 1, :b => 2, :c => 3}
keys = [:a, :b]
values = hash.values_at(*keys) #=> [1, 2]
transposed_matrix =[keys, values].transpose #=> [[:a, 1], [:b, 2]]
Hash[transposed_matrix] #=> {:a => 1, :b => 2}
Wenn Sie der Meinung sind, dass das Patchen von Affen der richtige Weg ist, möchten Sie Folgendes:
module MyExtension
module Hash
def slice(*keys)
::Hash[[keys, self.values_at(*keys)].transpose]
end
def except(*keys)
desired_keys = self.keys - keys
::Hash[[desired_keys, self.values_at(*desired_keys)].transpose]
end
end
end
Hash.include MyExtension::Hash
Ruby 2.5 hat Hash # Slice hinzugefügt :
h = { a: 100, b: 200, c: 300 }
h.slice(:a) #=> {:a=>100}
h.slice(:b, :c, :d) #=> {:b=>200, :c=>300}
Sie können Slice! (* -Tasten) verwenden, die in den Kernerweiterungen von ActiveSupport verfügbar sind
initial_hash = {:a => 1, :b => 2, :c => 3, :d => 4}
extracted_slice = initial_hash.slice!(:a, :c)
initial_hash wäre jetzt
{:b => 2, :d =>4}
extract_slide wäre jetzt
{:a => 1, :c =>3}
Sie können anschauen slice.rb in ActiveSupport 3.1.3
module HashExtensions
def subhash(*keys)
keys = keys.select { |k| key?(k) }
Hash[keys.zip(values_at(*keys))]
end
end
Hash.send(:include, HashExtensions)
{:a => :A, :b => :B, :c => :C, :d => :D}.subhash(:a) # => {:a => :A}
def subhash(*keys) select {|k,v| keys.include?(k)} end
h1 = {:a => :A, :b => :B, :c => :C, :d => :D}
keys = [:b, :d, :e, :f]
h2 = (h1.keys & keys).each_with_object({}) { |k,h| h.update(k=>h1.delete(k)) }
#=> {:b => :B, :d => :D}
h1
#=> {:a => :A, :c => :C}
class Hash
def extract(*keys)
key_index = Hash[keys.map{ |k| [k, true] }] # depends on the size of keys
partition{ |k, v| key_index.has_key?(k) }.map{ |group| Hash[group] }
end
end
h1 = {:a => :A, :b => :B, :c => :C, :d => :D}
h2, h1 = h1.extract(:b, :d, :e, :f)
Hier ist ein schneller Leistungsvergleich der vorgeschlagenen Methoden, #select
scheint der schnellste zu sein
k = 1_000_000
Benchmark.bmbm do |x|
x.report('select') { k.times { {a: 1, b: 2, c: 3}.select { |k, _v| [:a, :b].include?(k) } } }
x.report('hash transpose') { k.times { Hash[ [[:a, :b], {a: 1, b: 2, c: 3}.fetch_values(:a, :b)].transpose ] } }
x.report('slice') { k.times { {a: 1, b: 2, c: 3}.slice(:a, :b) } }
end
Rehearsal --------------------------------------------------
select 1.640000 0.010000 1.650000 ( 1.651426)
hash transpose 1.720000 0.010000 1.730000 ( 1.729950)
slice 1.740000 0.010000 1.750000 ( 1.748204)
----------------------------------------- total: 5.130000sec
user system total real
select 1.670000 0.010000 1.680000 ( 1.683415)
hash transpose 1.680000 0.010000 1.690000 ( 1.688110)
slice 1.800000 0.010000 1.810000 ( 1.816215)
Die Verfeinerung wird folgendermaßen aussehen:
module CoreExtensions
module Extractable
refine Hash do
def extract(*keys)
select { |k, _v| keys.include?(k) }
end
end
end
end
Und um es zu benutzen:
using ::CoreExtensions::Extractable
{ a: 1, b: 2, c: 3 }.extract(:a, :b)
Beide delete_if
und keep_if
sind Teil des Ruby-Kerns. Hier können Sie erreichen, was Sie möchten, ohne den Hash
Typ zu patchen .
h1 = {:a => :A, :b => :B, :c => :C, :d => :D}
h2 = h1.clone
p h1.keep_if { |key| [:b, :d, :e, :f].include?(key) } # => {:b => :B, :d => :D}
p h2.delete_if { |key, value| [:b, :d, :e, :f].include?(key) } #=> {:a => :A, :c => :C}
Weitere Informationen finden Sie unter den folgenden Links in der Dokumentation:
Wie bereits erwähnt, hat Ruby 2.5 die Hash # -Slice-Methode hinzugefügt.
Rails 5.2.0beta1 hat auch eine eigene Version von Hash # Slice hinzugefügt, um die Funktionalität für Benutzer des Frameworks zu verbessern, die eine frühere Version von Ruby verwenden. https://github.com/rails/rails/commit/01ae39660243bc5f0a986e20f9c9bff312b1b5f8
Wenn Sie aus irgendeinem Grund Ihre eigenen implementieren möchten, ist dies auch ein guter Einzeiler:
def slice(*keys)
keys.each_with_object(Hash.new) { |k, hash| hash[k] = self[k] if has_key?(k) }
end unless method_defined?(:slice)
Dieser Code fügt die von Ihnen angeforderte Funktionalität in die Hash-Klasse ein:
class Hash
def extract_subhash! *keys
to_keep = self.keys.to_a - keys
to_delete = Hash[self.select{|k,v| !to_keep.include? k}]
self.delete_if {|k,v| !to_keep.include? k}
to_delete
end
end
und erzeugt die von Ihnen angegebenen Ergebnisse:
h1 = {:a => :A, :b => :B, :c => :C, :d => :D}
p h1.extract_subhash!(:b, :d, :e, :f) # => {b => :B, :d => :D}
p h1 #=> {:a => :A, :c => :C}
Hinweis: Diese Methode gibt tatsächlich die extrahierten Schlüssel / Werte zurück.
Hier ist eine funktionale Lösung, die nützlich sein kann, wenn Sie nicht mit Ruby 2.5 arbeiten und Ihre Hash-Klasse nicht durch Hinzufügen einer neuen Methode verschmutzen möchten:
slice_hash = -> keys, hash { hash.select { |k, _v| keys.include?(k) } }.curry
Dann können Sie es auch auf verschachtelte Hashes anwenden:
my_hash = [{name: "Joe", age: 34}, {name: "Amy", age: 55}]
my_hash.map(&slice_hash.([:name]))
# => [{:name=>"Joe"}, {:name=>"Amy"}]
Nur eine Ergänzung zur Slice-Methode: Wenn die Subhash-Schlüssel, die Sie vom ursprünglichen Hash trennen möchten, dynamisch sind, können Sie Folgendes tun:
slice(*dynamic_keys) # dynamic_keys should be an array type
Wir können dies tun, indem wir nur die Schlüssel schleifen, die wir extrahieren möchten, und nur überprüfen, ob der Schlüssel vorhanden ist, und ihn dann extrahieren.
class Hash
def extract(*keys)
extracted_hash = {}
keys.each{|key| extracted_hash[key] = self.delete(key) if self.has_key?(key)}
extracted_hash
end
end
h1 = {:a => :A, :b => :B, :c => :C, :d => :D}
h2 = h1.extract(:b, :d, :e, :f)