Victor Ronin
Victor Ronin

Reputation: 23268

Ruby hash with multiple keys pointing to the same value

I am looking for a way to have, I would say synonym keys in the hash.

I want multiple keys to point to the same value, so I can read/write a value through any of these keys.

As example, it should work like that (let say :foo and :bar are synonyms)

hash[:foo] = "foo"
hash[:bar] = "bar"
puts hash[:foo] # => "bar"

Update 1

Let me add couple of details. The main reason why I need these synonyms, because I receive keys from external source, which I can't control, but multiple keys could actually be associated with the same value.

Upvotes: 3

Views: 13349

Answers (4)

Volty De Qua
Volty De Qua

Reputation: 290

The answer to your original post is:

hash[:foo] = hash[:bar]

and

hash[:foo].__id__ == hash[:bar].__id__it

will hold true as long as the value is a reference value (String, Array ...) .


The answer to your Update 1 could be:

input.reduce({ :k => {}, :v => {} }) { |t, (k, v)| 
        t[:k][t[:v][v] || k] = v;
        t[:v][v] = k;
        t
    }[:k]

where «input» is an abstract enumerator (or array) of your input data as it comes [key, value]+, «:k» your result, and «:v» an inverted hash that serves the purpose of finding a key if its value is already present.

Upvotes: 1

Todd A. Jacobs
Todd A. Jacobs

Reputation: 84343

Rethink Your Data Structure

Depending on how you want to access your data, you can make either the keys or the values synonyms by making them an array. Either way, you'll need to do more work to parse the synonyms than the definitional word they share.

Keys as Definitions

For example, you could use the keys as the definition for your synonyms.

# Create your synonyms.
hash = {}
hash['foo'] = %w[foo bar]
hash
# => {"foo"=>["foo", "bar"]}

# Update the "definition" of your synonyms.
hash['baz'] = hash.delete('foo')
hash
# => {"baz"=>["foo", "bar"]}

Values as Definitions

You could also invert this structure and make your keys arrays of synonyms instead. For example:

hash = {["foo", "bar"]=>"foo"}
hash[hash.rassoc('foo').first] = 'baz'
=> {["foo", "bar"]=>"baz"}

Upvotes: 9

dbenhur
dbenhur

Reputation: 20408

You could subclass hash and override [] and []=.

class AliasedHash < Hash
  def initialize(*args)
    super
    @aliases = {}
  end

  def alias(from,to)
    @aliases[from] = to
    self
  end

  def [](key)
    super(alias_of(key))
  end

  def []=(key,value)
    super(alias_of(key), value)
  end

  private
  def alias_of(key)
    @aliases.fetch(key,key)
  end
end

ah = AliasedHash.new.alias(:bar,:foo)

ah[:foo] = 123
ah[:bar] # => 123
ah[:bar] = 456
ah[:foo] # => 456

Upvotes: 5

Gavin Miller
Gavin Miller

Reputation: 43815

What you can do is completely possible as long as you assign the same object to both keys.

variable_a = 'a'
hash = {foo: variable_a, bar: variable_a}

puts hash[:foo] #=> 'a'
hash[:bar].succ!
puts hash[:foo] #=> 'b'

This works because hash[:foo] and hash[:bar] both refer to the same instance of the letter a via variable_a. This however wouldn't work if you used the assignment hash = {foo: 'a', bar: 'a'} because in that case :foo and :bar refer to different instance variables.

Upvotes: 1

Related Questions