如何从一个哈希表中提取子哈希表?

109

我有一个哈希表:

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}

5
附注:http://apidock.com/rails/Hash/slice%21 - tokland
1
@JanDvorak 这个问题不仅涉及返回子哈希,还涉及修改现有的哈希。非常相似的东西,但 ActiveSupport 处理它们的方式不同。 - skalee
17个回答

2
正如其他人所提到的,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)

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)

这段话的意思是:“细化后将会呈现如下所示:”。其中包含HTML标签,我不会进行解释。
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)

考虑到基准测试只针对一个数据集进行,并且结果都非常接近,我质疑你的结论“#select seems to be the fastest”是否有统计依据。另外,我重新运行了你的基准测试(在2022年3月,使用纯Ruby),发现“slice”几乎比其他两个快三倍。 - Cary Swoveland

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

这段代码将你所要求的功能注入到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

关于切片方法的补充,如果你想从原始哈希中分离出动态子哈希键,可以这样做:

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)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接