Làm cách nào để trích xuất một hàm băm phụ từ một hàm băm?


95

Tôi có một hàm băm:

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

Cách tốt nhất để trích xuất một băm phụ như thế này là gì?

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


có thể trùng lặp của của Ruby Hash Lọc
John Dvorak

1
@JanDvorak Câu hỏi này không chỉ về việc trả về subhash mà còn về việc sửa đổi subhash hiện có. Những thứ rất giống nhau nhưng ActiveSupport có những cách khác nhau để giải quyết chúng.
skalee

Câu trả lời:


58

Nếu bạn đặc biệt muốn phương thức trả về các phần tử đã trích xuất nhưng h1 vẫn giữ nguyên:

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} 

Và nếu bạn muốn vá nó vào lớp 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

Nếu bạn chỉ muốn xóa các phần tử được chỉ định khỏi hàm băm, thì điều đó sẽ dễ dàng hơn nhiều bằng cách sử dụng 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
Đây là O (n2) - bạn sẽ có một vòng lặp trên vùng chọn, một vòng lặp khác trên bao gồm sẽ được gọi là h1.size times.
metakungfu

1
Trong khi câu trả lời này là khá cho ruby tinh khiết, nếu bạn đang sử dụng đường ray, dưới đây trả lời (sử dụng built-in slicehoặc except, tùy thuộc vào nhu cầu của bạn) có nhiều bụi
Krease

137

ActiveSupport, ít nhất kể từ 2.3.8, cung cấp bốn phương pháp thuận tiện: #slice , #exceptvà các đối tác phá hoại của họ: #slice!#except!. Chúng đã được đề cập trong các câu trả lời khác, nhưng để tổng hợp chúng ở một nơi:

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}

Lưu ý các giá trị trả về của các phương thức bang. Chúng sẽ không chỉ điều chỉnh hàm băm hiện có mà còn trả về các mục nhập đã xóa (không được giữ lại). Các Hash#except!bộ quần áo phù hợp nhất với ví dụ được đưa ra trong câu hỏi:

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}

ActiveSupportkhông yêu cầu toàn bộ Rails, khá nhẹ. Trên thực tế, rất nhiều ngọc không phải đường ray phụ thuộc vào nó, vì vậy hầu hết có thể bạn đã có nó trong Gemfile.lock. Không cần phải tự mình mở rộng lớp Hash.


3
Kết quả của x.except!(:c, :d)(với tiếng nổ) phải là # => {:a=>1, :b=>2}. Tốt nếu bạn có thể chỉnh sửa câu trả lời của mình.
244an

28

Nếu bạn sử dụng đường ray , Hash # Slice là cách để đi.

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

Nếu bạn không sử dụng rails , Hash # values_at sẽ trả về các giá trị theo thứ tự như bạn đã yêu cầu, vì vậy bạn có thể thực hiện việc này:

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

Ví dụ:

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

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

Giải trình:

Ngoài {:a => 1, :b => 2, :c => 3}chúng tôi muốn{: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}

Nếu bạn cảm thấy như con khỉ là cách để đi, sau đây là những gì bạn muốn:

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 vá chắc chắn là cách để đi IMO. Gọn gàng hơn nhiều và làm cho ý định rõ ràng hơn.
Romário

1
Thêm để sửa đổi mã để địa chỉ mô-đun lõi một cách chính xác, xác định mô-đun và nhập mở rộng lõi băm ... mô-đun CoreExtensions mô-đun Mô-đun CoreExtensions băm (* các phím) :: Hash [[các khóa, self.values_at (* các khóa)]. Transpose] end end kết thúc Hash.include CoreExtensions :: Hash
Ronan Fauglas


5

Bạn có thể sử dụng Slice! (Phím *) có sẵn trong các phần mở rộng cốt lõi của ActiveSupport

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

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

Initial_hash bây giờ sẽ là

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

Extract_slide bây giờ sẽ là

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

Bạn có thể nhìn vào slice.rb in ActiveSupport 3.1.3


Tôi nghĩ rằng bạn đang mô tả trích xuất !. trích xuất! xóa các khóa khỏi hàm băm ban đầu, trả về một hàm băm mới chứa các khóa đã bị loại bỏ. lát! thực hiện ngược lại: loại bỏ tất cả trừ các khóa được chỉ định khỏi hàm băm ban đầu (một lần nữa, trả về một hàm băm mới chứa các khóa đã loại bỏ). Vì vậy, lát! giống như thao tác "giữ lại" hơn một chút.
Russ Egan

1
ActiveSupport không phải là một phần của 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
Công việc tốt. Không hoàn toàn những gì anh ấy yêu cầu. Phương thức của bạn trả về: {: d =>: D,: b =>: B,: e => nil,: f => nil} {: c =>: C,: a =>: A,: d => : D,: b =>: B}
Andy

Giải pháp một dòng tương đương (và có lẽ nhanh hơn): <pre> def subhash(*keys) select {|k,v| keys.include?(k)} end
cao điểm

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

nếu bạn sử dụng đường ray, có thể thuận tiện khi sử dụng 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

Dưới đây là so sánh hiệu suất nhanh chóng của các phương pháp được đề xuất, #selectcó vẻ là nhanh nhất

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)

Quá trình sàng lọc sẽ như thế này:

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

Và để sử dụng nó:

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

1

Cả hai delete_ifkeep_ifđều là một phần của lõi Ruby. Ở đây bạn có thể đạt được những gì bạn muốn mà không cần phải vá lỗi 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}

Để biết thêm thông tin, hãy kiểm tra các liên kết bên dưới từ tài liệu:


1

Như những người khác đã đề cập, Ruby 2.5 đã thêm phương thức Hash # slice.

Rails 5.2.0beta1 cũng đã thêm phiên bản riêng của lát cắt Hash # để cung cấp chức năng cho người dùng khung đang sử dụng phiên bản Ruby trước đó. https://github.com/rails/rails/commit/01ae39660243bc5f0a986e20f9c9bff312b1b5f8

Nếu bạn đang tìm kiếm để triển khai của riêng bạn vì bất kỳ lý do gì, đó cũng là một lớp lót tốt:

 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

Mã này đưa chức năng bạn đang yêu cầu vào lớp 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

và tạo ra kết quả bạn đã cung cấp:

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}

Lưu ý: phương thức này thực sự trả về các khóa / giá trị được trích xuất.


0

Đây là một giải pháp chức năng có thể hữu ích nếu bạn không chạy trên Ruby 2.5 và trong trường hợp bạn không thể làm ô nhiễm lớp Hash của mình bằng cách thêm một phương thức mới:

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

Sau đó, bạn có thể áp dụng nó ngay cả trên các hàm băm lồng nhau:

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

0

Chỉ là một phần bổ sung cho phương thức slice, nếu các khóa subhash mà bạn muốn tách khỏi hàm băm ban đầu sẽ động, bạn có thể làm như,

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

0

Chúng tôi có thể làm điều đó bằng cách lặp lại các khóa mà chúng tôi muốn giải nén và chỉ cần kiểm tra khóa tồn tại và sau đó giải nén nó.

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)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.