Hash Invert in Ruby

Reverse a hash in Ruby

You could convert the Hash to an Array, reverse that, and then convert it back to a Hash:

reversed_h = Hash[h.to_a.reverse]

Hash#to_a gives you an array of arrays, the inner arrays are simple [key,value] pairs, then you reverse that array using Array#reverse, and Hash[] converts the [key,value] pairs back into a Hash.

Ruby 2.1 adds an Array#to_h method so you can now say:

reversed_h = h.to_a.reverse.to_h

Hash invert in Ruby?

hash = {:key1 => ["a", "b", "c"], :key2 => ["d", "e", "f"]}

first variant

hash.map{|k, v| v.map{|f| {f => k}}}.flatten
#=> [{"a"=>:key1}, {"b"=>:key1}, {"c"=>:key1}, {"d"=>:key2}, {"e"=>:key2}, {"f"=>:key2}]

or

hash.inject({}){|h, (k,v)| v.map{|f| h[f] = k}; h}
#=> {"a"=>:key1, "b"=>:key1, "c"=>:key1, "d"=>:key2, "e"=>:key2, "f"=>:key2}

UPD

ok, your hash is:

hash = {"status"=>{"1"=>["1", "14"], "2"=>["7", "12", "8", "13"]}}
hash["status"].inject({}){|h, (k,v)| v.map{|f| h[f] = k}; h}
#=> {"12"=>"2", "7"=>"2", "13"=>"2", "8"=>"2", "14"=>"1", "1"=>"1"}

Ruby - How to invert a Hash with an array values?

h = {"Book Y"=>["author B", "author C"], "Book X"=>["author A", "author B", "author C"]}

p h.inject(Hash.new([])) { |memo,(key,values)|
values.each { |value| memo[value] += [key] }
memo
}
# => {"author B"=>["Book Y", "Book X"], "author C"=>["Book Y", "Book X"], "author A"=>["Book X"]}

How to swap keys and values in a hash

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

What are some nice ways to reverse a nested hash?

This can be accomplished with a recursive method for inverting the keys of the hash (and values, if desired). For example:

hsh = {{"c"=>"b"}=>"a"}
def recursive_invert(hsh)
hsh.each_with_object({}) do |(k, v), inverted_hsh|
if k.is_a? Hash
k = recursive_invert(k)
end
inverted_hsh[v] = k
end
end
recursive_invert(hsh) # {"a"=>{"b"=>"c"}}

Ruby: Invert a hash to also preserve non unique values

I do prefer @Jikku's solution, but there's always another way. Here's one.
(I see this is very close to @Chris's solution. I will leave it for the last line, which is a little different.)

Code

def inside_out(h)
g = h.flat_map { |s,a| a.product([s]) }
.group_by(&:first)
g.merge(g) { |_,a| a.map(&:last) }
end

Example

h = {"a" => [1, 2, 3], "b" => [4, 5, 6], "c" => [3, 4, 5], "d" => [7, 2, 3]}

inside_out(h)
#=> {1=>["a"], 2=>["a", "d"], 3=>["a", "c", "d"], 4=>["b", "c"],
# 5=>["b", "c"], 6=>["b"], 7=>["d"]}

Explanation

For h above:

a = h.flat_map { |s,a| a.product([s]) }
#=> [[1, "a"], [2, "a"], [3, "a"], [4, "b"], [5, "b"], [6, "b"],
# [3, "c"], [4, "c"], [5, "c"], [7, "d"], [2, "d"], [3, "d"]]
g = a.group_by(&:first)
#=> {1=>[[1, "a"]], 2=>[[2, "a"], [2, "d"]],
# 3=>[[3, "a"], [3, "c"], [3, "d"]],
# 4=>[[4, "b"], [4, "c"]],
# 5=>[[5, "b"], [5, "c"]],
# 6=>[[6, "b"]],
# 7=>[[7, "d"]]}
g.merge(g) { |_,a| a.map(&:last) }
#=> {1=>["a"], 2=>["a", "d"], 3=>["a", "c", "d"], 4=>["b", "c"],
# 5=>["b", "c"], 6=>["b"], 7=>["d"]}

Algorithm for Deep Hash Invert (should be in ruby)

Code

def recurse(h, arr=[])
h.each_with_object({}) { |(k,v),g| g.update((Hash===v) ?
recurse(v, arr + [k]) : { v=>[arr+[k]] }) { |_,o,n| o+n } }
end

The recursion uses the form of Hash#update (aka merge!) that employs the block { |_,o,n| o+n } } to determine the values of keys that are present in both hashes being merged.

Example 1

h =
{
u: {
u: { u: :phe, c: :phe, a: :leu, g: :leu },
c: { u: :ser, c: :ser, a: :ser, g: :ser },
a: { u: :tyr, c: :tyr, a: :STOP, g: :STOP },
g: { u: :cys, c: :cys, a: :STOP, g: :trp }
},
c: {
u: { u: :leu, c: :leu, a: :leu, g: :leu },
c: { u: :pro, c: :pro, a: :pro, g: :pro },
a: { u: :his, c: :his, a: :gln, g: :gln },
g: { u: :arg, c: :arg, a: :arg, g: :arg }
},
}

recurse h
#=> {:phe=>[[:u, :u, :u], [:u, :u, :c]],
# :leu=>[[:u, :u, :a], [:u, :u, :g], [:c, :u, :u],
# [:c, :u, :c], [:c, :u, :a], [:c, :u, :g]],
# :ser=>[[:u, :c, :u], [:u, :c, :c], [:u, :c, :a], [:u, :c, :g]],
# :tyr=>[[:u, :a, :u], [:u, :a, :c]],
# :STOP=>[[:u, :a, :a], [:u, :a, :g], [:u, :g, :a]],
# :cys=>[[:u, :g, :u], [:u, :g, :c]],
# :trp=>[[:u, :g, :g]],
# :pro=>[[:c, :c, :u], [:c, :c, :c], [:c, :c, :a], [:c, :c, :g]],
# :his=>[[:c, :a, :u], [:c, :a, :c]],
# :gln=>[[:c, :a, :a], [:c, :a, :g]],
# :arg=>[[:c, :g, :u], [:c, :g, :c], [:c, :g, :a], [:c, :g, :g]]}

Example 2

h =
{
u: {
u: { u: :phe, a: :leu },
c: { u: :ser, c: :phe },
a: { u: :tyr, c: { a: { u: :leu, c: :ser }, u: :tyr } }
},
c: {
u: { u: :leu, c: :pro },
a: { u: :arg }
},
}

recurse(h)
#=> {:phe=>[[:u, :u, :u], [:u, :c, :c]],
# :leu=>[[:u, :u, :a], [:u, :a, :c, :a, :u], [:c, :u, :u]],
# :ser=>[[:u, :c, :u], [:u, :a, :c, :a, :c]],
# :tyr=>[[:u, :a, :u], [:u, :a, :c, :u]],
# :pro=>[[:c, :u, :c]], :arg=>[[:c, :a, :u]]}

Explanation

Here is the code modified to display the calculations that are being performed:

def recurse(h, arr=[], level = 0)
indent = ' '*(2*level)
puts "#{indent}level = #{level}"
puts "#{indent}h= #{h}"
puts "#{indent}arr= #{arr}"
g = h.each_with_object({}) do |(k,v),g|
puts "#{indent} level = #{level}"
puts "#{indent} k=#{k}"
puts "#{indent} v=#{v}"
puts "#{indent} g=#{g}"
case v
when Hash
puts "#{indent} v is Hash"
g.update(recurse(v, arr + [k], level+1)) { |_,o,n| o+n }
else
puts "#{indent} v is not a Hash"
g.update({ v=>[arr+[k]] }) { |_,o,n| o+n }
end
end
puts "#{indent}return #{g}"
g
end

The output for recurse h follows, for Example 2 (for diehards only).

level = 0
h= {:u=>{:u=>{:u=>:phe, :a=>:leu}, :c=>{:u=>:ser, :c=>:phe}, :a=>{:u=>:tyr, :c=>{:a=>{:u=>:leu, :c=>:ser}, :u=>:tyr}}}, :c=>{:u=>{:u=>:leu, :c=>:pro}, :a=>{:u=>:arg}}}
arr= []
level = 0
k=u
v={:u=>{:u=>:phe, :a=>:leu}, :c=>{:u=>:ser, :c=>:phe},
:a=>{:u=>:tyr, :c=>{:a=>{:u=>:leu, :c=>:ser}, :u=>:tyr}}}
g={}
v is Hash
level = 1
h= {:u=>{:u=>:phe, :a=>:leu}, :c=>{:u=>:ser, :c=>:phe},
:a=>{:u=>:tyr, :c=>{:a=>{:u=>:leu, :c=>:ser}, :u=>:tyr}}}
arr= [:u]
level = 1
k=u
v={:u=>:phe, :a=>:leu}
g={}
v is Hash
level = 2
h= {:u=>:phe, :a=>:leu}
arr= [:u, :u]
level = 2
k=u
v=phe
g={}
v is not a Hash
level = 2
k=a
v=leu
g={:phe=>[[:u, :u, :u]]}
v is not a Hash
return {:phe=>[[:u, :u, :u]], :leu=>[[:u, :u, :a]]}
level = 1
k=c
v={:u=>:ser, :c=>:phe}
g={:phe=>[[:u, :u, :u]], :leu=>[[:u, :u, :a]]}
v is Hash
level = 2
h= {:u=>:ser, :c=>:phe}
arr= [:u, :c]
level = 2
k=u
v=ser
g={}
v is not a Hash
level = 2
k=c
v=phe
g={:ser=>[[:u, :c, :u]]}
v is not a Hash
return {:ser=>[[:u, :c, :u]], :phe=>[[:u, :c, :c]]}
level = 1
k=a
v={:u=>:tyr, :c=>{:a=>{:u=>:leu, :c=>:ser}, :u=>:tyr}}
g={:phe=>[[:u, :u, :u], [:u, :c, :c]], :leu=>[[:u, :u, :a]], :ser=>[[:u, :c, :u]]}
v is Hash
level = 2
h= {:u=>:tyr, :c=>{:a=>{:u=>:leu, :c=>:ser}, :u=>:tyr}}
arr= [:u, :a]
level = 2
k=u
v=tyr
g={}
v is not a Hash
level = 2
k=c
v={:a=>{:u=>:leu, :c=>:ser}, :u=>:tyr}
g={:tyr=>[[:u, :a, :u]]}
v is Hash
level = 3
h= {:a=>{:u=>:leu, :c=>:ser}, :u=>:tyr}
arr= [:u, :a, :c]
level = 3
k=a
v={:u=>:leu, :c=>:ser}
g={}
v is Hash
level = 4
h= {:u=>:leu, :c=>:ser}
arr= [:u, :a, :c, :a]
level = 4
k=u
v=leu
g={}
v is not a Hash
level = 4
k=c
v=ser
g={:leu=>[[:u, :a, :c, :a, :u]]}
v is not a Hash
return {:leu=>[[:u, :a, :c, :a, :u]], :ser=>[[:u, :a, :c, :a, :c]]}
level = 3
k=u
v=tyr
g={:leu=>[[:u, :a, :c, :a, :u]], :ser=>[[:u, :a, :c, :a, :c]]}
v is not a Hash
return {:leu=>[[:u, :a, :c, :a, :u]], :ser=>[[:u, :a, :c, :a, :c]],
:tyr=>[[:u, :a, :c, :u]]}
return {:tyr=>[[:u, :a, :u], [:u, :a, :c, :u]], :leu=>[[:u, :a, :c, :a, :u]],
:ser=>[[:u, :a, :c, :a, :c]]}
return {:phe=>[[:u, :u, :u], [:u, :c, :c]], :leu=>[[:u, :u, :a], [:u, :a, :c, :a, :u]],
:ser=>[[:u, :c, :u], [:u, :a, :c, :a, :c]], :tyr=>[[:u, :a, :u], [:u, :a, :c, :u]]}
level = 0
k=c
v={:u=>{:u=>:leu, :c=>:pro}, :a=>{:u=>:arg}}
g={:phe=>[[:u, :u, :u], [:u, :c, :c]], :leu=>[[:u, :u, :a], [:u, :a, :c, :a, :u]],
:ser=>[[:u, :c, :u], [:u, :a, :c, :a, :c]], :tyr=>[[:u, :a, :u], [:u, :a, :c, :u]]}
v is Hash
level = 1
h= {:u=>{:u=>:leu, :c=>:pro}, :a=>{:u=>:arg}}
arr= [:c]
level = 1
k=u
v={:u=>:leu, :c=>:pro}
g={}
v is Hash
level = 2
h= {:u=>:leu, :c=>:pro}
arr= [:c, :u]
level = 2
k=u
v=leu
g={}
v is not a Hash
level = 2
k=c
v=pro
g={:leu=>[[:c, :u, :u]]}
v is not a Hash
return {:leu=>[[:c, :u, :u]], :pro=>[[:c, :u, :c]]}
level = 1
k=a
v={:u=>:arg}
g={:leu=>[[:c, :u, :u]], :pro=>[[:c, :u, :c]]}
v is Hash
level = 2
h= {:u=>:arg}
arr= [:c, :a]
level = 2
k=u
v=arg
g={}
v is not a Hash
return {:arg=>[[:c, :a, :u]]}
return {:leu=>[[:c, :u, :u]], :pro=>[[:c, :u, :c]], :arg=>[[:c, :a, :u]]}
return {:phe=>[[:u, :u, :u], [:u, :c, :c]],
:leu=>[[:u, :u, :a], [:u, :a, :c, :a, :u], [:c, :u, :u]],
:ser=>[[:u, :c, :u], [:u, :a, :c, :a, :c]],
:tyr=>[[:u, :a, :u], [:u, :a, :c, :u]],
:pro=>[[:c, :u, :c]],
:arg=>[[:c, :a, :u]]}
#=> <the last value returned above>

Inverting a hash value (that's an array) into new individual keys

There are many ways to do this. Here is one:

Hash[lumpy_hash.map { |k,v| v.product([k]) }.first]
#=> {"A"=>1, "B"=>1}

I don't think the method Hash#invert is useful here.

The steps:

enum = lumpy_hash.map
#=> #<Enumerator: {1=>["A", "B"]}:map>
k,v = enum.next
#=> [1, ["A", "B"]]
k #=> 1
v #=> ["A", "B"]
a = v.product([k])
#=> ["A", "B"].product([1])
#=> [["A", 1], ["B", 1]]
Hash[a]
#=> {"A"=>1, "B"=>1}

Here's another way that makes use of a hash's default value. This one is rather interesting:

key,value = lumpy_hash.to_a.first
#=> [1, ["A","B"]]
Hash.new { |h,k| h[k]=key }.tap { |h| h.values_at(*value) }
#=> {"A"=>1,"B"=>1}

Object#tap passes an empty hash to its block, assigning it to the block variable h. The block returns h after adding three key-value pairs, each having a value equal to the hash's default value. It adds the pairs merely by computing the values of keys the hash doesn't have!

how do I invert my hash (switch key/value) and group by value

Not too hard!

hash = { "unique_id" => "1",
"unique_id2" => "2",
"unique_id3" => "n"
}
new_hash = hash.each_with_object({}) { |(k,v), h| (h[v] ||= []) << k }

each_with_object({}) is just an each loop with a blank hash

||= [] means if the hash doesn't have a value for v, set it equal to an empty array

<< k pushes the key onto the array



Related Topics



Leave a reply



Submit