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

  • Recipe 5.5, "Using an Array or Other Modifiable Object as a Hash Key"
  • "True Inversion of a Hash in Ruby," by Tilo Sloboda (http://www.unixgods.org/~tilo/Ruby/invert_hash.html)
  • The Facets library defines a Hash#inverse method much like safe_invert


Strings

Numbers

Date and Time

Arrays

Hashes

Files and Directories

Code Blocks and Iteration

Objects and Classes8

Modules and Namespaces

Reflection and Metaprogramming

XML and HTML

Graphics and Other File Formats

Databases and Persistence

Internet Services

Web Development Ruby on Rails

Web Services and Distributed Programming

Testing, Debugging, Optimizing, and Documenting

Packaging and Distributing Software

Automating Tasks with Rake

Multitasking and Multithreading

User Interface

Extending Ruby with Other Languages

System Administration



Ruby Cookbook
Ruby Cookbook (Cookbooks (OReilly))
ISBN: 0596523696
EAN: 2147483647
Year: N/A
Pages: 399

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net