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}
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âu trả lời:
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}
slice
hoặc except
, tùy thuộc vào nhu cầu của bạn) có nhiều bụi
ActiveSupport
, ít nhất kể từ 2.3.8, cung cấp bốn phương pháp thuận tiện: #slice
, #except
và các đối tác phá hoại của họ: #slice!
và #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}
ActiveSupport
khô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.
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.
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
Ruby 2.5 đã thêm lát cắt # băm :
h = { a: 100, b: 200, c: 300 }
h.slice(:a) #=> {:a=>100}
h.slice(:b, :c, :d) #=> {:b=>200, :c=>300}
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
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}
def subhash(*keys) select {|k,v| keys.include?(k)} end
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}
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)
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, #select
có 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)
Cả hai delete_if
và keep_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:
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)
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.
Đâ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"}]
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
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)