Ruby中的双向哈希表

时间:2011-08-03 12:13:33

标签: ruby hash bidirectional

我需要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. 双向哈希意味着fetchrfetch都应该在固定时间内执行。
  4. Ruby中是否存在这样的结构(包括外部库)?

    我考虑过使用两个单向Hashes同步修改它们中的一个(并将其打包到类中以避免同步问题),但是我可以使用现有的解决方案吗?

5 个答案:

答案 0 :(得分:7)

你可以很容易地自己构建一些东西,只需使用一个包含两个哈希的简单对象(一个用于向前方向,一个用于反向)。例如:

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

查找将像普通哈希查找一样运行(因为它们正常哈希查找)。添加一些运算符,可能是不错的to_sinspect实现,你很好。

这样的事情是这样的:

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

在您需要时构建工具没有任何问题。

答案 1 :(得分:5)

Ruby中没有内置的这种结构。

请注意Hash#rassoc执行类似的操作,但它只返回第一个匹配项并且是线性时间:

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

此外,无法以完全安全的方式在Ruby中满足您的要求,因为您将无法检测到数组值的更改。 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??

每次计算哈希以检测更改都会使您的查找成为线性时间。你可以复制数组值并冻结它们(就像Ruby对于字符串的哈希键一样!)

您似乎需要的是一个Graph类,它可能具有与Hash不同的API,不是吗?您可以查看rgl或类似内容,但我不知道它们是如何实施的。

祝你好运。

答案 2 :(得分:1)

有一个Hash#invert方法(http://www.ruby-doc.org/core-2.1.0/Hash.html#method-i-invert)来实现这一目标。但它不会将多个值映射到数组。

答案 3 :(得分:0)

试试这个:

class Hash
  def rfetch val
    select { |k,v| v.is_a?(Array) ? v.include?(val) : v == val }.map { |x| x[0] }
  end
end

答案 4 :(得分:0)

如果您没有对此哈希进行大量更新,则可以使用inverthash