ruby有效的方法来获取给定值的多个哈希键

时间:2010-12-04 23:27:06

标签: ruby

从给定值获取所有哈希键的最有效方法是什么。

my_hash = {"a"=>"aa", "b"=>"bb", "c"=>"bb"}

我想将散列“bb”作为输入值,并将所有键(b,c)作为数组返回

只返回一个键:

my_hash.index("bb")
# returns only b

这有效但似乎效率低下:

my_hash.select{|k,v| v == 'bb' }.map{|i| i[0] }
# returns b and c

我已阅读所有文档。我觉得我有一些显而易见的东西。

谢谢!

更新

我最终切换了哈希创建的键和值,并使用数组来表示值。这是一种更有效的解决方案。如果必要,请参阅下文,了解进行价值观察的最佳方法。

新结构:

my_hash = {"aa"=>["a"],"bb"=>["b","c"]}

4 个答案:

答案 0 :(得分:30)

稍快一点:

my_hash.map{ |k,v| v=='bb' ? k : nil }.compact

仅对小哈希和单个查询较慢。如果需要为多个值请求反向映射,则速度更快。如果这对您的应用程序很重要,我建议您保留一张反向地图。

rev = Hash.new{ |h,k| h[k]=[] }
my_hash.each{ |k,v| rev[v] << k }
rev['bb']

基准:

require 'benchmark'
N = 1_000_000
my_hash = {"a"=>"aa", "b"=>"bb", "c"=>"bb"}

Benchmark.bmbm do |x|
  x.report('select/map'){ N.times{
    my_hash.select{|k,v|v=='bb'}.map{|i| i[0]}
  }}
  x.report('select/map/destructure'){ N.times{
    my_hash.select{|k,v|v=='bb'}.map{|k,v| k}
  }}
  x.report('map/compact'){ N.times{
    my_hash.map{|k,v|v=='bb' ? k : nil}.compact        
  }}
  x.report('reverse map'){ N.times{
    rev = Hash.new{|h,k|h[k]=[]}
    my_hash.each{ |k,v| rev[v]<<k }
    rev['bb']
  }}
  x.report('reject'){ N.times{
    my_hash.reject{|k,v|v != "bb"}.keys
  }}
end
#=> Rehearsal ----------------------------------------------------------
#=> select/map               1.950000   0.000000   1.950000 (  1.950137)
#=> select/map/destructure   1.960000   0.010000   1.970000 (  1.963740)
#=> map/compact              1.200000   0.000000   1.200000 (  1.197340)
#=> reverse map              3.660000   0.000000   3.660000 (  3.658245)
#=> reject                   2.110000   0.000000   2.110000 (  2.115805)
#=> ------------------------------------------------ total: 10.890000sec
#=> 
#=>                              user     system      total        real
#=> select/map               1.950000   0.000000   1.950000 (  1.948784)
#=> select/map/destructure   1.970000   0.010000   1.980000 (  1.966636)
#=> map/compact              1.190000   0.000000   1.190000 (  1.192052)
#=> reverse map              3.670000   0.000000   3.670000 (  3.664798)
#=> reject                   2.140000   0.000000   2.140000 (  2.135069)

答案 1 :(得分:4)

通过设计,关联数据结构可以快速查找给定密钥的值。它们不能有效地按值查找密钥。

在C ++中,std :: map不能有效地执行此操作,但是boost :: bimap可以。我不知道它是如何在下面工作的,但从逻辑上讲它可以通过创建两个映射(键值和键值)和一个界面使它看起来像是一个来工作。你可以做同样的事情。由于你的地图在一个方向看起来是单值的,而在另一个方向看起来是多值的(即没有一对一的对应关系),我怀疑有很多预建的库可以完全按照你想要的那样做。

免责声明:我不认识Ruby。

答案 2 :(得分:4)

由于哈希值甚至没有按值排序(可能允许使用二进制搜索,因此可能会加速一点),但是如果没有遍历整个哈希值,你就不会找到这样做的方法,所以你的解决方案实际上看起来效率最高。

替代方案可能是:

my_hash.reject{|k,v|v != "bb"}.keys

代码略短,但恕我直言也比你的版本更难理解。此外,reject方法构建了哈希的副本,因此它的内存密集程度更高。如果您不关心原始哈希,请使用就地工作的delete_if:

my_hash.delete_if{|k,v|v != "bb"}.keys

答案 3 :(得分:2)

另一种选择:my_hash.find_all{|k,v|v == "bb"}.map(&:first)

不是最快的,但对人眼很好:)。

使用 Phrogz的基准:

Rehearsal ----------------------------------------------------------
select/map               3.604000   0.000000   3.604000 (  3.638208)
select/map/destructure   3.682000   0.000000   3.682000 (  3.678210)
map/compact              2.620000   0.000000   2.620000 (  2.620150)
reverse map              5.991000   0.000000   5.991000 (  5.985342)
reject                   3.572000   0.000000   3.572000 (  3.612207)
find_all/map             2.964000   0.000000   2.964000 (  2.965169)
------------------------------------------------ total: 22.433000sec

                             user     system      total        real
select/map               3.619000   0.000000   3.619000 (  3.634207)
select/map/destructure   3.698000   0.000000   3.698000 (  3.702212)
map/compact              2.589000   0.000000   2.589000 (  2.620150)
reverse map              5.913000   0.000000   5.913000 (  6.013344)
reject                   3.557000   0.000000   3.557000 (  3.569204)
find_all/map             2.948000   0.000000   2.948000 (  2.959169)