Inverting a Hash






Inverting a Hash

Problem

Given a hash, you want to switch the keys and values. That is, you want to create a new hash whose keys are the values of the old hash, and whose values are the keys of the old hash. If the old hash mapped "human" to "wolf;" you want the new hash to map "wolf" to "human."

Solution

The simplest technique is to use the Hash#invert method:

	phone_directory = {     'Alice' => '555-1212',
	                        'Bob' => '555-1313',
	                        'Mallory' => '111-1111' }
	phone_directory.invert
	# => {"111-1111"=>"Mallory", "555-1212"=>"Alice", "555-1313"=>"Bob"}

Discussion

Hash#invert probably won't do what you want if your hash maps more than one key to the same value. Only one of the keys for that value will show up as a value in the inverted hash:

	phone_directory = {     'Alice' => '555-1212',
	                        'Bob' => '555-1313',
	                        'Carol' => '555-1313',
	                        'Mallory' => '111-1111',
	                        'Ted' => '555-1212' }
	phone_directory.invert
	# => {"111-1111"=>"Mallory", "555-1212"=>"Ted", "555-1313"=>"Bob"}

To preserve all the data from the original hash, borrow the idea behind Recipe 5.6, and write a version of invert that keeps an array of values for each key. The following is based on code by Tilo Sloboda:

	class Hash
	  def safe_invert
	    new_hash = {}
	    self.each do |k,v|
	      if v.is_a? Array
	        v.each { |x| new_hash.add_or_append(x, k) }
	      else
	        new_hash.add_or_append(v, k)
	      end
	    end
	    return new_hash
	  end

The add_or_append method a lot like the method MultivaluedHash#[]= defined in Recipe 5.6:

	  def add_or_append(key, value)
	    if has_key?(key)
	      self[key] = [value, self[key]].flatten
	    else
	      self[key] = value
	    end
	  end
	end

Here's safe_invert in action:

	phone_directory.safe_invert
	# => {"111-1111"=>"Mallory", "555-1212"=>["Ted", "Alice"],
	# "555-1313"=>["Bob", "Carol"]}

	phone_directory.safe_invert.safe_invert
	# => {"Alice"=>"555-1212", "Mallory"=>"111-1111", "Ted"=>"555-1212",
	# => "Carol"=>"555-1313", "Bob"=>"555-1313"}

Ideally, if you called an inversion method twice you'd always get the same data you started with. The safe_invert method does better than invert on this score, but it's not perfect. If your original hash used arrays as hash keys, safe_invert will act as if you'd individually mapped each element in the array to the same value. Call safe_invert twice, and the arrays will be gone.

See Also



 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows