11

我需要一个 Ruby 中的双向哈希表。例如:

h = {:abc => 123, :xyz => 789, :qaz => 789, :wsx => [888, 999]}
h.fetch(:xyz) # => 789
h.rfetch(123) # => abc
h.rfetch(789) # => [:xyz, :qaz]
h.rfetch(888) # => :wsx

方法rfetch意味着反向获取,只是我的建议。

注意三点:

  1. 如果多个键映射到相同的值,则rfetch返回所有键,并打包在数组中。
  2. 如果 value 是一个数组,则rfetch在数组的元素中查找它的参数。
  3. 双向哈希意味着两者都fetch应该rfetch在恒定时间内执行。

Ruby(包括外部库)中是否存在这种结构?

我考虑过在修改其中一个时使用两个同步的单向哈希来实现它(并将其打包到类中以避免同步问题),但也许我可以使用已经存在的解决方案?

4

5 回答 5

7

You could build something yourself pretty easily, just use a simple object that wraps two hashes (one for the forward direction, one for the reverse). For example:

class BiHash
    def initialize
        @forward = Hash.new { |h, k| h[k] = [ ] }
        @reverse = Hash.new { |h, k| h[k] = [ ] }
    end

    def insert(k, v)
        @forward[k].push(v)
        @reverse[v].push(k)
        v
    end

    def fetch(k)
        fetch_from(@forward, k)
    end

    def rfetch(v)
        fetch_from(@reverse, v)
    end

    protected

    def fetch_from(h, k)
        return nil if(!h.has_key?(k))
        v = h[k]
        v.length == 1 ? v.first : v.dup
    end
end

Look ups will behave just like normal hash lookups (because they are normal hash lookups). Add some operators and maybe decent to_s and inspect implementations and you're good.

Such a thing works like this:

b = BiHash.new
b.insert(:a, 'a')
b.insert(:a, 'b')
b.insert(:a, 'c')
b.insert(:b, 'a')
b.insert(:c, 'x')

puts b.fetch(:a).inspect            # ["a", "b", "c"]
puts b.fetch(:b).inspect            # "a"
puts b.rfetch('a').inspect          # [:a, :b]
puts b.rfetch('x').inspect          # :c
puts b.fetch(:not_there).inspect    # nil
puts b.rfetch('not there').inspect  # nil

There's nothing wrong with building your tools when you need them.

于 2011-08-03T18:32:10.763 回答
5

There is no such structure built-in in Ruby.

Note that Hash#rassoc does something similar, but it returns only the first match and is linear-time:

h = {:abc => 123, :xyz => 789, :qaz => 789, :wsx => [888, 999]}
h.rassoc(123) # => [:abc, 123]

Also, it isn't possible to fullfill your requirements in Ruby in a perfectly safe manner, as you won't be able to detect changes in values that are arrays. E.g.:

h = MyBidirectionalArray.new(:foo => 42, :bar => [:hello, :world])
h.rfetch(:world) # => :bar
h[:bar].shift
h[:bar] # => [:world]
h.rfetch(:world) # => should be nil, but how to detect this??

Computing a hash everytime to detect a change will make your lookup linear-time. You could duplicate the array-values and freeze them, though (like Ruby does for Hash keys that are strings!)

What you seem to need is a Graph class, which could have a different API than a Hash, no? You can check out rgl or similar, but I don't know how they're implemented.

Good luck.

于 2011-08-03T14:44:05.263 回答
3

There is a Hash#invert method (http://www.ruby-doc.org/core-2.1.0/Hash.html#method-i-invert) to achieve this. It won't map multiple values to an array though.

于 2014-03-15T15:15:29.617 回答
0

试试这个:

class Hash
  def rfetch val
    select { |k,v| v.is_a?(Array) ? v.include?(val) : v == val }.map { |x| x[0] }
  end
end
于 2011-08-03T12:41:12.257 回答
0

If you're not doing lots of updates to this hash, you might be able to use inverthash.

于 2011-08-03T14:59:18.647 回答