Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bidirectional Hash table in Ruby

I need a bidirectional Hash table in Ruby. For example:

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

Method rfetch means reversed fetch and is only my proposal.

Note three things:

  1. If multiple keys map at the same value then rfetch returns all of them, packed in array.
  2. If value is an array then rfetch looks for its param among elements of the array.
  3. Bidirectional Hash means that both fetch and rfetch should execute in constant time.

Does such structure exists in Ruby (including external libraries)?

I thought about implementing it using two one-directional Hashes synchronized when one of them is modified (and packing it into class to avoid synchronization problems) but maybe I could use an already existing solution?

like image 942
Mikołaj Pastuszko Avatar asked Aug 03 '11 12:08

Mikołaj Pastuszko


People also ask

How does Ruby calculate hash value?

To find a hash key by it's value, i.e. reverse lookup, one can use Hash#key . It's available in Ruby 1.9+.

What is hashing in Ruby?

In Ruby, Hash is a collection of unique keys and their values. Hash is like an Array, except the indexing is done with the help of arbitrary keys of any object type. In Hash, the order of returning keys and their value by various iterators is arbitrary and will generally not be in the insertion order.

How do you get the key of a hash in Ruby?

Ruby | Hash key() functionHash#key() is a Hash class method which gives the key value corresponding to the value. If value doesn't exist then return nil.


1 Answers

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.

like image 200
mu is too short Avatar answered Oct 28 '22 20:10

mu is too short