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


95

해시가 있습니다.

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}



1
@JanDvorak이 질문은 하위 해시 반환뿐만 아니라 기존 해시 수정에 관한 것입니다. 매우 비슷하지만 ActiveSupport는 다른 방법으로 처리합니다.
skalee 2013-12-05

답변:


58

메서드가 추출 된 요소를 반환하지만 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} 

2
이것은 O (n2)입니다. select에 하나의 루프가 있고 h1.size 번 호출되는 include에 또 다른 루프가 있습니다.
metakungfu

1
이 대답은 순수 루비에 대한 괜찮은하지만 당신이 레일을 사용하는 경우, 대답은 아래 (사용하여 내장 slice또는 except, 필요에 따라서는) 훨씬 청소기입니다
Krease

137

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 클래스를 확장 할 필요가 없습니다.


3
x.except!(:c, :d)(with bang) 의 결과는 # => {:a=>1, :b=>2}. 답변을 수정할 수 있다면 좋습니다.
244an

28

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

{: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

2
Mokey 패치는 확실히 IMO로가는 길입니다. 훨씬 깨끗하고 의도를 더 명확하게 만듭니다.
Romário

1
corectly 코어 모듈을 처리하기 위해 코드를 수정하고, 모듈을 정의하고, Hash core 확장을 가져옵니다. 모듈 CoreExtensions 모듈 Hash def slice (* keys) :: Hash [[keys, self.values_at (* keys)]. transpose] end end end Hash.include CoreExtensions :: Hash
Ronan Fauglas 2016 년


5

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


나는 당신이 추출물을 묘사하고 있다고 생각합니다!. 추출물! 초기 해시에서 키를 제거하고 제거 된 키를 포함하는 새 해시를 반환합니다. 일부분! 반대는 않습니다 모두 제거 하지만 (제거 된 키를 포함하는 새로운 해시를 반환, 다시) 초기 해시에서 지정된 키를. 그래서 슬라이스! "유지"작업과 좀 더 비슷합니다.
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
좋은 작업. 그가 요구하는 것과는 다릅니다. 메서드는 다음을 반환합니다. {: d => : D, : b => : B, : e => nil, : f => nil} {: c => : C, : a => : A, : d => : D, : b => : B}
Andy

동등한 단선 (그리고 아마도 더 빠른) 솔루션 : <pre> def subhash(*keys) select {|k,v| keys.include?(k)} end
정점

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

레일을 사용하는 경우 Hash를 사용하는 것이 편리 할 수 ​​있습니다.

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

다음은 제안 된 방법의 빠른 성능 비교입니다 #select. 가장 빠른 것 같습니다.

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)

개선은 다음과 같습니다.

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

그리고 그것을 사용하려면 :

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

1

모두 delete_ifkeep_if루비 코어의 일부입니다. 여기에서 Hash유형 을 패치하지 않고도 원하는 것을 얻을 수 있습니다 .

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}

자세한 내용은 설명서에서 아래 링크를 확인하십시오.


1

다른 사람들이 언급했듯이 Ruby 2.5는 Hash # slice 메서드를 추가했습니다.

Rails 5.2.0beta1은 또한 이전 버전의 Ruby를 사용하는 프레임 워크 사용자를위한 기능을 shim하기 위해 자체 버전의 Hash # slice를 추가했습니다. https://github.com/rails/rails/commit/01ae39660243bc5f0a986e20f9c9bff312b1b5f8

어떤 이유로 든 자신의 것을 구현하려는 경우에도 좋은 라이너입니다.

 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

이 코드는 요청하는 기능을 Hash 클래스에 삽입합니다.

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

제공 한 결과를 생성합니다.

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}

참고 :이 메서드는 실제로 추출 된 키 / 값을 반환합니다.


0

다음은 Ruby 2.5에서 실행하지 않고 새 메서드를 추가하여 Hash 클래스를 오염시키지 않으려는 경우에 유용 할 수있는 기능적 솔루션입니다.

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

그런 다음 중첩 된 해시에도 적용 할 수 있습니다.

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

0

슬라이스 방법에 추가로, 원래 해시와 분리하려는 하위 해시 키가 동적 일 경우 다음과 같이 할 수 있습니다.

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

0

추출하려는 키만 반복하고 키가 존재하는지 확인한 다음 추출하면됩니다.

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)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.