How to swap keys and values in a hash

RubyHashmap

Ruby Problem Overview


How do I swap keys and values in a Hash?

I have the following Hash:

{:a=>:one, :b=>:two, :c=>:three}

that I want to transform into:

{:one=>:a, :two=>:b, :three=>:c}

Using map seems rather tedious. Is there a shorter solution?

Ruby Solutions


Solution 1 - Ruby

Ruby has a helper method for Hash that lets you treat a Hash as if it was inverted (in essence, by letting you access keys through values):

{a: 1, b: 2, c: 3}.key(1)
=> :a

If you want to keep the inverted hash, then Hash#invert should work for most situations:

{a: 1, b: 2, c: 3}.invert
=> {1=>:a, 2=>:b, 3=>:c}

BUT...

If you have duplicate values, invert will discard all but the last occurrence of your values (because it will keep replacing new value for that key during iteration). Likewise, key will only return the first match:

{a: 1, b: 2, c: 2}.key(2)
=> :b

{a: 1, b: 2, c: 2}.invert
=> {1=>:a, 2=>:c}

So, if your values are unique you can use Hash#invert. If not, then you can keep all the values as an array, like this:

class Hash
  # like invert but not lossy
  # {"one"=>1,"two"=>2, "1"=>1, "2"=>2}.inverse => {1=>["one", "1"], 2=>["two", "2"]} 
  def safe_invert
    each_with_object({}) do |(key,value),out| 
      out[value] ||= []
      out[value] << key
    end
  end
end

Note: This code with tests is now on GitHub.

Or:

class Hash
  def safe_invert
    self.each_with_object({}){|(k,v),o|(o[v]||=[])<<k}
  end
end

Solution 2 - Ruby

You bet there is one! There is always a shorter way to do things in Ruby!

It's pretty simple, just use Hash#invert:

{a: :one, b: :two, c: :three}.invert
=> {:one=>:a, :two=>:b, :three=>:c}

Et voilĂ !

Solution 3 - Ruby

files = {
  'Input.txt' => 'Randy',
  'Code.py' => 'Stan',
  'Output.txt' => 'Randy'
}

h = Hash.new{|h,k| h[k] = []} # Create hash that defaults unknown keys to empty an empty list
files.map {|k,v| h[v]<< k} #append each key to the list at a known value
puts h

This will handle the duplicate values too.

Solution 4 - Ruby

If you have a hash where are the keys are unique, you can use Hash#invert:

> {a: 1, b: 2, c: 3}.invert
=> {1=>:a, 2=>:b, 3=>:c} 

That won't work if you have non unique keys, however, where only the last keys seen will be kept:

> {a: 1, b: 2, c: 3, d: 3, e: 2, f: 1}.invert
=> {1=>:f, 2=>:e, 3=>:d}

If you have a hash with non unique keys, you might do:

> hash={a: 1, b: 2, c: 3, d: 3, e: 2, f: 1}
> hash.each_with_object(Hash.new { |h,k| h[k]=[] }) {|(k,v), h| 
            h[v] << k
            }     
=> {1=>[:a, :f], 2=>[:b, :e], 3=>[:c, :d]}

If the values of the hash are already arrays, you can do:

> hash={ "A" => [14, 15, 16], "B" => [17, 15], "C" => [35, 15] }
> hash.each_with_object(Hash.new { |h,k| h[k]=[] }) {|(k,v), h| 
            v.map {|t| h[t] << k}
            }   
=> {14=>["A"], 15=>["A", "B", "C"], 16=>["A"], 17=>["B"], 35=>["C"]}

Solution 5 - Ruby

# this doesn't looks quite as elegant as the other solutions here,
# but if you call inverse twice, it will preserve the elements of the original hash

# true inversion of Ruby Hash / preserves all elements in original hash
# e.g. hash.inverse.inverse ~ h

class Hash

  def inverse
    i = Hash.new
    self.each_pair{ |k,v|
      if (v.class == Array)
        v.each{ |x|
          i[x] = i.has_key?(x) ? [k,i[x]].flatten : k
        }
      else
        i[v] = i.has_key?(v) ? [k,i[v]].flatten : k
      end
    }
    return i
  end

end

Hash#inverse gives you:

 h = {a: 1, b: 2, c: 2}
 h.inverse
  => {1=>:a, 2=>[:c, :b]}
 h.inverse.inverse
  => {:a=>1, :c=>2, :b=>2}  # order might not be preserved
 h.inverse.inverse == h
  => true                   # true-ish because order might change

whereas the built-in invert method is just broken:

 h.invert
  => {1=>:a, 2=>:c}    # FAIL
 h.invert.invert == h 
  => false             # FAIL

Solution 6 - Ruby

Using Array

input = {:key1=>"value1", :key2=>"value2", :key3=>"value3", :key4=>"value4", :key5=>"value5"}
output = Hash[input.to_a.map{|m| m.reverse}]

Using Hash

input = {:key1=>"value1", :key2=>"value2", :key3=>"value3", :key4=>"value4", :key5=>"value5"}
output = input.invert

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJonathan AllardView Question on Stackoverflow
Solution 1 - RubyNigel ThorneView Answer on Stackoverflow
Solution 2 - RubyJonathan AllardView Answer on Stackoverflow
Solution 3 - RubyRiazeView Answer on Stackoverflow
Solution 4 - RubydawgView Answer on Stackoverflow
Solution 5 - RubyTiloView Answer on Stackoverflow
Solution 6 - RubyPrashant Ravi DarshanView Answer on Stackoverflow