ฉันจะแยกแฮชย่อยออกจากแฮชได้อย่างไร


98

ฉันมีแฮช:

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
tokland

สามารถทำซ้ำได้ของRuby Hash Filter
John Dvorak

1
@JanDvorak คำถามนี้ไม่เพียง แต่เกี่ยวกับการส่งคืน subhash แต่ยังเกี่ยวกับการแก้ไขที่มีอยู่ด้วย สิ่งที่คล้ายกันมาก แต่ ActiveSupport มีวิธีจัดการที่แตกต่างกัน
skalee

คำตอบ:


59

หากคุณต้องการให้เมธอดส่งคืนองค์ประกอบที่แยกออกมาโดยเฉพาะ แต่ 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) - คุณจะมีหนึ่งลูปในการเลือกและอีกลูปหนึ่งในการรวมซึ่งจะเรียกว่า h1.size times
metakungfu

2
แม้ว่าคำตอบนี้เหมาะสำหรับทับทิมแท้ แต่หากคุณใช้รางคำตอบด้านล่าง (ใช้ในตัวsliceหรือexceptขึ้นอยู่กับความต้องการของคุณ) จะสะอาดกว่ามาก
Krease

140

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ไม่ต้องใช้ Rails ทั้งหมดมีน้ำหนักเบาพอสมควร อันที่จริงอัญมณีที่ไม่ใช่รางรถไฟจำนวนมากขึ้นอยู่กับมันดังนั้นส่วนใหญ่คุณอาจมีอยู่แล้วใน Gemfile.lock ไม่จำเป็นต้องขยายคลาส Hash ด้วยตัวคุณเอง


3
ผลมาจากx.except!(:c, :d)(โครมคราม) # => {:a=>1, :b=>2}ควรจะเป็น ถ้าคุณแก้ไขคำตอบได้ก็ดี
244 น.

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 รวม CoreExtensions :: Hash
Ronan Fauglas


5

คุณสามารถใช้ slice! (* keys) ซึ่งมีอยู่ในส่วนขยายหลักของ ActiveSupport

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}



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เป็นส่วนหนึ่งของ Ruby core ที่นี่คุณสามารถบรรลุสิ่งที่คุณต้องการโดยไม่ต้องแก้ไข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 ยังเพิ่ม Hash # slice เวอร์ชันของตัวเองเพื่อแสดงการทำงานสำหรับผู้ใช้เฟรมเวิร์กที่ใช้ Ruby เวอร์ชันก่อนหน้า 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 แล้วหากคีย์ย่อยที่คุณต้องการแยกออกจากแฮชดั้งเดิมจะเป็นแบบไดนามิกคุณสามารถทำได้เช่น

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.