Ruby 如何从哈希中提取子哈希?

Ruby 如何从哈希中提取子哈希?,ruby,hash,Ruby,Hash,我有一个杂烩: 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} 如果您特别希望该方法返回提取的元素,但h1保持不变: h1 = {:a => :A, :b => :B,

我有一个杂烩:

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}

如果您特别希望该方法返回提取的元素,但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
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
如果您只想从散列中删除指定的元素,那么使用它会容易得多


此代码将您要求的功能注入哈希类:

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
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}

注意:此方法实际上返回提取的键/值。

您可以使用slice*ActiveSupport的核心扩展中提供的密钥

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}
initial_hash = {:a => 1, :b => 2, :c => 3, :d => 4}

extracted_slice = initial_hash.slice!(:a, :c)
最初的_散列现在是

{:b => 2, :d =>4}
{:a => 1, :c =>3}
这张幻灯片现在应该是

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

您可以查看ActiveSupport 3.1.3中的slice.rb,如果您使用rails,那么使用Hash.rb可能会很方便

h = {a:1, b:2}
h1 = h.except(:a) # {b:2}
至少从2.3.8开始,ActiveSupport就提供了四种方便的方法:slice,except和它们的破坏性对应方法:slice!除了!。在其他回答中提到了这些问题,但概括起来:

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方法的返回值。它们不仅将定制现有哈希,还将返回删除的未保留的条目。哈什除外!最适合问题中给出的示例:

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,它非常轻量级。事实上,很多非rails gem都依赖于它,所以很可能您已经在Gemfile.lock中有了它。不需要自己扩展Hash类。

如果使用rails,这是一个不错的选择

{:a => :A, :b => :B, :c => :C, :d => :D}.slice(:a, :c)
# =>  {:a => :A, :c => :C}
如果不使用rails,则可以执行以下操作:

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

说明:

在{:a=>1,:b=>2,:c=>3}中,我们想要{: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

下面是建议方法的快速性能比较,选择似乎是最快的

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)
增加:


如果您不是在Ruby 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"}]
delete_if和keep_if都是Ruby核心的一部分。在这里,您可以在不修补哈希类型的情况下实现所需的功能

h1={:a=>:a,:b=>:b,:c=>:c,:d=>:d} h2=h1.5 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} 有关更多信息,请查看文档中的以下链接:


正如其他人提到的,Ruby 2.5添加了Hashslice方法

Rails 5.2.0beta1还添加了自己版本的Hashslice,为使用Ruby早期版本的框架用户填充功能。

如果出于任何原因希望实现您自己的,那么它也是一个不错的单行程序:

 def slice(*keys)
   keys.each_with_object(Hash.new) { |k, hash| hash[k] = self[k] if has_key?(k) }
 end unless method_defined?(:slice)

只是对slice方法的一个补充,如果要从原始散列中分离的子散列键是动态的,您可以这样做

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

我们可以通过循环我们只想提取的密钥来实现,只需检查密钥是否存在,然后提取它

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)

干得好。不完全是他想要的。你的方法返回:{:d=>:d,:b=>:b,:e=>nil,:f=>nil}{:c=>:c,:a=>:a,:d=>:d,:b=>:b}一个等价的一行,可能更快的解决方案:def subhash*键选择{k,v |键。include?k}尾端注意:@JanDvorak的可能重复这个问题不仅是关于返回subhash,还关于修改现有的。非常相似的事情,但是ActiveSupport有不同的方法来处理它们。我想你是在描述extract!。摘录从初始哈希中删除密钥,返回包含已删除密钥的新哈希。片执行相反的操作:再次从初始哈希中删除除指定键以外的所有键,返回包含已删除键的新哈希。切吧!更像是一个保留操作。ActiveSupport不是Ruby缝合的一部分这是在2-你将在select上有一个循环,在include上有另一个循环,称为h1.size times。虽然这个答案对于纯Ruby来说很合适,但如果你使用rails,下面的答案使用内置切片或except,根据您的需要,它会更干净。如果答案正确,请参见下文x.except的结果!:c、 带有bang的:d应该是=>{:a=>1,:b=>2}。如果你能编辑你的答案,那就好了。这应该是正确的答案。密钥修补绝对是IMO的一种方式。更清晰,更清晰。添加到修改代码以正确处理核心模块,定义模块并导入扩展哈希核心。。。模块CoreExtensions模块哈希定义切片*键::哈希[[键,self.values_at*键]。转置]结束哈希。包含CoreExtensions::哈希
slice(*dynamic_keys) # dynamic_keys should be an array type 
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)