Bir hash'den bir alt hash'i nasıl çıkarırım?


97

Bir hashim var:

h1 = {:a => :A, :b => :B, :c => :C, :d => :D}

Bunun gibi bir alt karma çıkartmanın en iyi yolu nedir?

h1.extract_subhash(:b, :d, :e, :f) # => {:b => :B, :d => :D}
h1 #=> {:a => :A, :c => :C}



1
@JanDvorak Bu soru sadece subhash döndürmekle ilgili değil, aynı zamanda var olanı değiştirmekle de ilgili. Çok benzer şeyler ancak ActiveSupport'un bunlarla başa çıkmak için farklı yolları vardır.
skalee

Yanıtlar:


59

Yöntemin özellikle çıkarılan öğeleri döndürmesini, ancak h1'in aynı kalmasını istiyorsanız:

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} 

Ve bunu Hash sınıfına eklemek istiyorsanız:

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

Sadece belirtilen öğeleri hash'den kaldırmak istiyorsanız, delete_if kullanmak çok daha kolaydır .

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} 

2
Bu O (n2) - seçimde bir döngü olacak, dahil etme üzerinde h1.size times olarak adlandırılan başka bir döngü olacak.
metakungfu

2
Bu cevap saf yakut için uygun olsa da, ray kullanıyorsanız, aşağıdaki cevap (yerleşik sliceveya exceptihtiyaçlarınıza bağlı olarak) çok daha temiz
Krease

140

ActiveSupport, 2.3.8 beri en az dört kullanışlı bir yöntem sağlar: #slice, #exceptve onların yıkıcı karşılıkları: #slice!ve #except!. Başka cevaplarda da bahsedildi, ancak bunları tek bir yerde özetlemek gerekirse:

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}

Bang yöntemlerinin dönüş değerlerini not edin. Yalnızca mevcut hash'i uyarlamakla kalmaz, aynı zamanda kaldırılan (tutulmayan) girişleri de iade eder. Hash#except!Takım elbise iyi söz verilen örnek:

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}

ActiveSupporttam Rails gerektirmez, oldukça hafiftir. Aslında, pek çok ray dışı mücevher buna bağlıdır, bu yüzden büyük olasılıkla zaten Gemfile.lock'ta zaten var. Hash sınıfını kendi başınıza genişletmenize gerek yok.


3
x.except!(:c, :d)(Patlama ile) sonucunun olması gerekir # => {:a=>1, :b=>2}. Cevabınızı düzenleyebiliyorsanız iyi.
244,

28

Ray kullanıyorsanız , Hash # slice gitmenin yoludur.

{:a => :A, :b => :B, :c => :C, :d => :D}.slice(:a, :c)
# =>  {:a => :A, :c => :C}

Eğer raylar kullanmazsanız , bunları sorduk olarak Hash # values_at aynı sırada değerlerini dönecektir Bunu böylece:

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

ör:

slice({foo: 'bar', 'bar' => 'foo', 2 => 'two'}, 'bar', 2) 
# => {'bar' => 'foo', 2 => 'two'}

except({foo: 'bar', 'bar' => 'foo', 2 => 'two'}, 'bar', 2) 
# => {:foo => 'bar'}

Açıklama:

Dışında {:a => 1, :b => 2, :c => 3}istiyoruz{: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}

Maymun yamasının gitmenin yolu olduğunu düşünüyorsanız, istediğiniz şey şudur:

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

2
Mokey yaması kesinlikle IMO'ya geçmenin yoludur. Çok daha temiz ve amacı daha net hale getiriyor.
Romário

1
Doğru çekirdek modülü adreslemek için kodu değiştirmek için ekleyin, modülü tanımlayın ve genişletme Hash core ... modülünü içe aktarın end Hash.include CoreExtensions :: Hash
Ronan Fauglas


5

ActiveSupport'un çekirdek uzantılarında bulunan dilim! (* Anahtarlarını) kullanabilirsiniz.

initial_hash = {:a => 1, :b => 2, :c => 3, :d => 4}

extracted_slice = initial_hash.slice!(:a, :c)

initial_hash artık

{:b => 2, :d =>4}

extracted_slide artık

{:a => 1, :c =>3}

Bakabilirsin slice.rb in ActiveSupport 3.1.3


Sanırım özü tarif ediyorsun !. Ayıkla! anahtarları başlangıç ​​karmasından kaldırır ve kaldırılan anahtarları içeren yeni bir karma döndürür. dilim! ters etmez: kaldırmak ancak (kaldırılan anahtarlarını ihtiva eden bir yeni karma dönen tekrar) ilk karma belirtilen anahtarlar. Öyleyse dilim! biraz daha "tutma" işlemine benzer.
Russ Egan

1
ActiveSupport, Ruby STI
Volte

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

1
İyi iş. Tam olarak istediği bu değil. Yönteminiz şunu döndürür: {: d =>: D,: b =>: B,: e => nil,: f => nil} {: c =>: C,: a =>: A,: d => : D
Andy

Eşdeğer bir tek satırlık (ve belki daha hızlı) çözüm: <pre> def subhash(*keys) select {|k,v| keys.include?(k)} end
en yoğun

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

2

ray kullanıyorsanız, Hash.except kullanmak uygun olabilir.

h = {a:1, b:2}
h1 = h.except(:a) # {b:2}

1
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)

1

İşte önerilen yöntemlerin hızlı bir performans karşılaştırması #select, en hızlı gibi görünüyor

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)

Ayrıntılandırma şöyle görünecek:

module CoreExtensions
  module Extractable
    refine Hash do
      def extract(*keys)
        select { |k, _v| keys.include?(k) }
      end
    end
  end
end

Ve kullanmak için:

using ::CoreExtensions::Extractable
{ a: 1, b: 2, c: 3 }.extract(:a, :b)

1

Hem delete_ifve keep_ifRuby iç parçasıdır. Burada, Hashtürü yamalamadan istediğinizi elde edebilirsiniz .

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}

Daha fazla bilgi için, belgelerden aşağıdaki bağlantılara bakın:


1

Diğerlerinin de belirttiği gibi Ruby 2.5, Hash # dilim yöntemini ekledi.

Rails 5.2.0beta1 ayrıca, Ruby'nin önceki bir sürümünü kullanan çerçeve kullanıcıları için işlevselliği bozmak için kendi Hash # dilim sürümünü ekledi. https://github.com/rails/rails/commit/01ae39660243bc5f0a986e20f9c9bff312b1b5f8

Herhangi bir nedenle kendinizinkini uygulamak istiyorsanız, bu da güzel bir astar:

 def slice(*keys)
   keys.each_with_object(Hash.new) { |k, hash| hash[k] = self[k] if has_key?(k) }
 end unless method_defined?(:slice)

0

Bu kod, istediğiniz işlevselliği Hash sınıfına enjekte eder:

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

ve sağladığınız sonuçları üretir:

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}

Not: Bu yöntem aslında çıkarılan anahtarları / değerleri döndürür.


0

Ruby 2.5'te çalışmıyorsanız ve yeni bir yöntem ekleyerek Hash sınıfınızı kirletmemek istemiyorsanız yararlı olabilecek işlevsel bir çözüm:

slice_hash = -> keys, hash { hash.select { |k, _v| keys.include?(k) } }.curry

Ardından iç içe geçmiş hash'lere bile uygulayabilirsiniz:

my_hash = [{name: "Joe", age: 34}, {name: "Amy", age: 55}]
my_hash.map(&slice_hash.([:name]))
# => [{:name=>"Joe"}, {:name=>"Amy"}]

0

Dilim yöntemine ek olarak, orijinal hash'den ayırmak istediğiniz subhash anahtarları dinamik olacaksa, beğenebilirsiniz,

slice(*dynamic_keys) # dynamic_keys should be an array type 

0

Bunu, sadece çıkarmak istediğimiz anahtarlar üzerinde döngü yaparak ve sadece anahtarın var olup olmadığını kontrol ederek ve sonra onu çıkararak yapabiliriz.

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)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.