program tip

해시에서 하위 해시를 어떻게 추출합니까?

radiobox 2020. 9. 13. 10:20
반응형

해시에서 하위 해시를 어떻게 추출합니까?


해시가 있습니다.

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

이와 같은 하위 해시를 추출하는 가장 좋은 방법은 무엇입니까?

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

메서드가 추출 된 요소를 반환하지만 h1은 동일하게 유지하도록하려는 경우 :

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} 

그리고 그것을 Hash 클래스에 패치하려면 :

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

해시에서 지정된 요소를 제거하려는 경우 delete_if를 사용하는 것이 훨씬 쉽습니다 .

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} 

ActiveSupport는 최소한 2.3.8부터 4 가지 편리한 방법을 제공합니다 : #slice, #except그리고 파괴적인 대응 방법 : #slice!#except!. 그들은 다른 답변에서 언급되었지만 한곳에서 요약했습니다.

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 메서드의 반환 값에 유의하십시오. 기존 해시를 조정할뿐만 아니라 제거 된 (보관되지 않은) 항목도 반환합니다. Hash#except!에 가장 적합한 질문에 주어진 예 :

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전체 레일이 필요하지 않으며 매우 가볍습니다. 사실, 많은 non-rails gem이 이것에 의존하기 때문에 아마도 이미 Gemfile.lock에있을 것입니다. 스스로 Hash 클래스를 확장 할 필요가 없습니다.


당신이 레일을 사용하는 경우 , 해시 # 슬라이스 길을 가야하는 것입니다.

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

당신이 레일을 사용하지 않는 경우 , 당신이 그들에게 물었다 같은 해시 #의 values_at는 동일한 순서로 값을 반환합니다 이 작업을 수행 할 수 있습니다 :

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

전의:

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 => 1, :b => 2, :c => 3}우리가 원하는{: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}

원숭이 패치가 갈 길이라고 생각되면 다음을 수행하십시오.

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 추가 Hash # slice :

h = { a: 100, b: 200, c: 300 }
h.slice(:a)           #=> {:a=>100}
h.slice(:b, :c, :d)   #=> {:b=>200, :c=>300}

ActiveSupport의 핵심 확장에서 사용할 수있는 slice! (* keys)를 사용할 수 있습니다.

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

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

initial_hash는 이제

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

extract_slide는 이제

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

당신은 볼 수 있습니다 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}

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}

if you use rails, it may be convenient to use Hash.except

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

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)

Here is a quick performance comparison of the suggested methods, #select seems to be the fastest

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)

The refinement will look like this:

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

And to use it:

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

Both delete_if and keep_if are part of Ruby core. Here you can achieve what you would like to without patching the Hash type.

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}

For futher info, check the links below from the documentation:


This code injects the functionality you're asking for into the Hash class:

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

and produces the results you provided:

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}

Note: this method actually returns the extracted keys/values.


Here's a functional solution that can be useful if you're not running on Ruby 2.5 and in the case that you don't wan't to pollute your Hash class by adding a new method:

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

Then you can apply it even on nested hashes:

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

As others have mentioned, Ruby 2.5 added the Hash#slice method.

Rails 5.2.0beta1 also added it's own version of Hash#slice to shim the functionality for users of the framework that are using an earlier version of Ruby. https://github.com/rails/rails/commit/01ae39660243bc5f0a986e20f9c9bff312b1b5f8

If looking to implement your own for whatever reason, it's a nice one liner as well:

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

Just an addition to slice method, if the subhash keys which you want to separate from original hash is going to be dynamic you can do like,

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

We can do it by looping on keys only we want to extract and just checking the key is exist and then extract it.

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)

참고URL : https://stackoverflow.com/questions/9025277/how-do-i-extract-a-sub-hash-from-a-hash

반응형