मैं एक हैश से उप-हैश कैसे निकालूं?


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}

4
साइड नोट: apidock.com/rails/Hash/slice%21
टोकन


1
@JDDvorak यह सवाल केवल सुभाष की वापसी के बारे में ही नहीं है, बल्कि मौजूदा को संशोधित करने के बारे में भी है। इसी तरह की चीजें लेकिन ActiveSupport के पास उनसे निपटने के लिए अलग-अलग साधन हैं।
स्केली

जवाबों:


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} 

और अगर आप हैश वर्ग में पैच करना चाहते हैं:

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) है - आपके पास चयन पर एक लूप होगा, जिसमें शामिल एक और लूप होगा जिसे h1.size बार कहा जाएगा।
मेकाकुंगफू

1
जबकि यह उत्तर शुद्ध रूबी के लिए सभ्य है, यदि आप रेल का उपयोग कर रहे हैं, तो नीचे का उत्तर ( आपकी आवश्यकताओं के आधार पर निर्मित sliceया except, का उपयोग करके ) ज्यादा साफ है
क्रीज़

137

ActiveSupport, 2.3.8 के बाद से कम से कम चार सुविधाजनक तरीके प्रदान: #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}

धमाके के तरीकों के रिटर्न वैल्यू पर ध्यान दें। वे न केवल मौजूदा हैश दर्जी करेंगे, बल्कि एंट्री हटाए गए (नहीं रखे गए) प्रविष्टि भी लौटाएंगे। 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पूरे रेल की आवश्यकता नहीं है, बहुत हल्का है। वास्तव में, बहुत सारे गैर-रेल रत्न इस पर निर्भर करते हैं, इसलिए संभवत: आपके पास यह पहले से ही Gemfile.lock में है। अपने दम पर हैश क्लास का विस्तार करने की आवश्यकता नहीं है।


3
x.except!(:c, :d)(धमाके के साथ) का परिणाम होना चाहिए # => {:a=>1, :b=>2}। यदि आप अपना उत्तर संपादित कर सकते हैं तो अच्छा है।
244an

28

यदि आप रेल का उपयोग करते हैं , तो हैश # स्लाइस जाने का रास्ता है।

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

यदि आप रेल का उपयोग नहीं करते हैं , तो Hash # 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 जाने का मार्ग है। बहुत क्लीनर और इरादे को साफ करता है।
रोमोरियो

1
कोरेक्टली कोर मॉड्यूल को संबोधित करने के लिए कोड को संशोधित करने के लिए, मॉड्यूल को परिभाषित करें और आयात को हैश कोर का विस्तार करें ... मॉड्यूल CoreExtensions मॉड्यूल हैश डिस स्लाइस (* चाबियाँ) :: हैश [[चाबियाँ, self.values_at (* चाबियाँ)]। अंत: प्रस्ताव। अंत Hash.include CoreExtensions :: हैश
रोनेन फौगालास


5

आप स्लाइस का उपयोग कर सकते हैं! (* कुंजियाँ) जो ActiveSupport के मुख्य एक्सटेंशन में उपलब्ध है

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

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

initial_hash अब होगा

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

अब निकाले जाएंगे

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

आप देख सकते हैं slice.rb in ActiveSupport 3.1.3


मुझे लगता है कि आप अर्क का वर्णन कर रहे हैं!। निकालने! प्रारंभिक हैश से कुंजी निकालता है, हटाए गए कुंजी वाले एक नए हैश को वापस करता है। टुकड़ा! इसके विपरीत है: प्रारंभिक हैश (फिर से, हटाए गए कुंजी वाले एक नए हैश लौटना) से सभी लेकिन निर्दिष्ट कुंजियों को हटा दें । इतना टुकड़ा! "रिटेन" ऑपरेशन की तरह थोड़ा अधिक है।
रस एगन

1
ActiveSupport रूबी 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}
एंडी

समतुल्य एक-पंक्ति (और शायद तेज़) समाधान: <प्री> def subhash(*keys) select {|k,v| keys.include?(k)} end
पीक


2

यदि आप रेल का उपयोग करते हैं, तो Hash.except का उपयोग करना सुविधाजनक हो सकता है

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_ifऔर keep_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

जैसा कि दूसरों ने उल्लेख किया है, रूबी 2.5 ने हैश # स्लाइस विधि को जोड़ा।

रूब 5.2.0beta1 ने यह भी कहा कि रूबी के पुराने संस्करण का उपयोग कर रहे फ्रेमवर्क के उपयोगकर्ताओं के लिए कार्यक्षमता को कम करने के लिए हैश # स्लाइस का अपना संस्करण है। 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

यह कोड उस कार्यक्षमता को इंजेक्ट करता है जिसे आप हैश क्लास में पूछ रहे हैं:

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

यहां एक कार्यात्मक समाधान है जो उपयोगी हो सकता है यदि आप रूबी 2.5 पर नहीं चल रहे हैं और इस मामले में कि आप एक नई विधि जोड़कर अपने हैश वर्ग को प्रदूषित नहीं करना चाहते हैं:

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)
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.