【问题标题】:ruby efficient way to get multiple hash keys for a given valueruby 获取给定值的多个哈希键的有效方法
【发布时间】:2011-05-20 09:01:39
【问题描述】:

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

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

【问题讨论】:

    标签: ruby


    【解决方案1】:

    另一种选择: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)
    

    【讨论】:

    • 你可以不使用Symbol#to_proc:.map(&:first)=>1.372s; .map{|x|x.first}=>1.357s; .map{|x|x[0]}=>1.28s
    【解决方案2】:

    稍微快一点:

    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 很好,如果可以的话,我会再为反向地图解决方案添加一个 +1。
    【解决方案3】:

    由于哈希甚至不是按值排序的(这可能会通过允许使用二进制搜索来加快速度),无论如何,如果不遍历整个哈希,您将无法找到这样做的方法,所以您的解决方案实际上看起来是最有效的解决方案。

    另一种可能是:

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

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

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

    【讨论】:

      【解决方案4】:

      通过设计,关联数据结构可以快速查找给定键的值。它们并非旨在高效地按值查找键。

      在 C++ 中 std::map 不能有效地做到这一点,但 boost::bimap 可以。我不知道它在下面是如何工作的,但从逻辑上讲,它可以通过创建两个映射(键到值和值到键)并制作一个接口来使它看起来像一个。你可以做同样的事情。由于您的地图似乎在一个方向上是单值的,而在另一个方向上是多值的(即没有一对一的对应关系),我怀疑是否有许多预建库可以完全满足您的需求。

      免责声明:我不知道 Ruby。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-10-05
        • 1970-01-01
        • 2013-08-23
        • 1970-01-01
        • 1970-01-01
        • 2016-03-02
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多