Hash invert to Ruby?

I have a hash of the format:

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

and I want to end up with:

 { a => key1, b => key1, c => key1, d => key2 ... } 

What is the easiest way to achieve this?

I am using Ruby on Rails.

UPDATE

OK I was able to extract the real object from the server log, it is pushed through AJAX.

  Parameters: {"status"=>{"1"=>["1", "14"], "2"=>["7", "12", "8", "13"]}} 
+6
source share
8 answers
 hash = {:key1 => ["a", "b", "c"], :key2 => ["d", "e", "f"]} 

first option

 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"} 
+7
source

Many other good answers. I just wanted to drop it for Ruby 2.0 and 1.9.3 too:

 hash = {apple: [1, 14], orange: [7, 12, 8, 13]} Hash[hash.flat_map{ |k, v| v.map{ |i| [i, k] } }] # => {1=>:apple, 14=>:apple, 7=>:orange, 12=>:orange, 8=>:orange, 13=>:orange} 

This is used: Hash::[] and Enumerable#flat_map

Also in these new versions there is Enumerable::each_with_object , which is very similar to Enumerable::inject / Enumerable::reduce :

 hash.each_with_object(Hash.new){ |(k, v), inverse| v.each{ |e| inverse[e] = k } } 

Performing a quick benchmark (Ruby 2.0.0p0; 2012 Macbook Air) using the original hash with 100 keys, each with 100 different values:

 Hash::[] w/ Enumerable#flat_map 155.7 (±9.0%) i/s - 780 in 5.066286s Enumerable#each_with_object w/ Enumerable#each 199.7 (±21.0%) i/s - 940 in 5.068926s 

Indicates that for this dataset, a faster version of each_with_object .

+3
source

Ok, suppose. You say you have an array, but I agree with Benoit that you probably have a hash. Functional approach:

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

also:

  h.map { |k, vs| Hash[vs.product([k])] }.inject(:merge) #=> {"a"=>:key1, "b"=>:key1, "c"=>:key1, "d"=>:key2, "e"=>:key2, "f"=>:key2} 
+2
source

In the case where the value matches more than one key, for example, "c" in this example ...

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

... some other answers will not give the expected result. We will need a reverse hash to store keys in arrays, for example:

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

This should do the trick:

 reverse = {} hash.each{ |k,vs| vs.each{ |v| reverse[v] ||= [] reverse[v] << k } } 

This was my precedent, and I would define my problem in the same way as the OP (in fact, finding the same phrase made me here), so I suspect this answer may help other searchers.

+2
source

If you want to change a hash formatted as follows:

 a = {:key1 => ["a", "b", "c"], :key2 => ["d", "e", "f"]} a.inject({}) do |memo, (key, values)| values.each {|value| memo[value] = key } memo end 

this returns:

 {"a"=>:key1, "b"=>:key1, "c"=>:key1, "d"=>:key2, "e"=>:key2, "f"=>:key2} 
+1
source
 new_hash={} hash = {"key1" => ['a', 'b', 'c'], "key2" => ['d','e','f']} hash.each_pair{|key, val|val.each{|v| new_hash[v] = key }} 

This gives

 new_hash # {"a"=>"key1", "b"=>"key1", "c"=>"key1", "d"=>"key2", "e"=>"key2", "f"=>"key2"} 
+1
source

If you want to handle duplicate values ​​correctly, then you should use Hash # inversion from Facets of Ruby

Hash#inverse keeps duplicate values , for example this ensures that hash.inverse.inverse == hash

or:

:

 require 'facets' h = {:key1 => [:a, :b, :c], :key2 => [:d, :e, :f]} => {:key1=>[:a, :b, :c], :key2=>[:d, :e, :f]} h.inverse => {:a=>:key1, :b=>:key1, :c=>:key1, :d=>:key2, :e=>:key2, :f=>:key2} 

The code is as follows:

 # 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 # eg 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 h = {:key1 => [:a, :b, :c], :key2 => [:d, :e, :f]} => {:key1=>[:a, :b, :c], :key2=>[:d, :e, :f]} h.inverse => {:a=>:key1, :b=>:key1, :c=>:key1, :d=>:key2, :e=>:key2, :f=>:key2} 
+1
source

One way to achieve what you are looking for:

 arr = [{["k1"] => ["a", "b", "c"]}, {["k2"] => ["d", "e", "f"]}] results_arr = [] arr.each do |hsh| hsh.values.flatten.each do |val| results_arr << { [val] => hsh.keys.first }··· end end Result: [{["a"]=>["k1"]}, {["b"]=>["k1"]}, {["c"]=>["k1"]}, {["d"]=>["k2"]}, {["e"]=>["k2"]}, {["f"]=>["k2"]}] 
0
source

Source: https://habr.com/ru/post/897818/


All Articles