2011-09-22 20 views
6

Tengo un hash del formato:Hash invert en Ruby?

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

y quiero terminar con:

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

¿Cuál es la forma más fácil de lograr esto?

Estoy usando Ruby on Rails.

ACTUALIZACIÓN

OK logré extraer el objeto real del registro del servidor, que está siendo empujado a través de AJAX.

Parameters: {"status"=>{"1"=>["1", "14"], "2"=>["7", "12", "8", "13"]}} 
+0

No estoy seguro de cómo imprimir una matriz para que pueda leerla y experimentar. – cjm2671

+0

Aún así, ¿qué has intentado? 'p array' imprime cosas. – Mat

+1

¿Estás seguro de que es una matriz y no un hash? La forma en que lo describes es ambigua. –

Respuesta

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

primera variante

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

o

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

bien, el hash es:

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

Esto se ve bien, pero no consigue que funcione; Actualicé la publicación ahora para mostrar el objeto real. – cjm2671

+0

ver mi actualización .. – fl00r

+0

como discutimos en usted la pregunta anterior, fl00r, usando inyectar cuando no hay necesidad de ello es IMHO no es una buena opción. Además, estás usando el mapa pero en realidad estás haciendo efectos secundarios, lo cual es confuso, cada uno es mejor (bueno, "mejor", cada uno suele ser malo). Sí, lo sé, soy un fanático funcional :-) – tokland

1

Si usted está mirando para revertir un hash con formato como esto, lo siguiente puede ayudarle a:

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

esto devuelve:

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

Esto da

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

Ok, supongamos. Usted dice que tiene una matriz, pero estoy de acuerdo con Benoit en que lo que probablemente tenga es un hash. Un enfoque funcional:

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} 

También:

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

Esto funciona con Ruby (> =) 1.9 solamente. –

+0

@undur_gongor: el primer fragmento debería funcionar en 1.8 – tokland

+0

Sigo recibiendo "' número impar de argumentos para Hash' ". 'Hash [vs ...]' debería ser 'Hash [* vs.map {| v | [v, k]} .flatten] 'y' inject (: merge) 'es solo 1.9. De todos modos, esto solo prueba que debería cambiar a 1.9 :-) –

0

Una forma de lograr lo que estás buscando:

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"]}] 
1

Si usted quiere tratar correctamente con valores duplicados, a continuación, debe usar el Hash # inverso de Facets of Ruby

Hash#inverse conserva los valores duplicados, p.asegura que hash.inverse.inverse == hash

ya sea:

como esto :

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} 

El código es el siguiente:

# 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 


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} 
2

En el caso en que un valor corresponde a más de una clave, como "c" en este ejemplo ...

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

... algunos de las otras respuestas no dará el resultado esperado. Vamos a necesitar el hash invertido para almacenar las claves en las matrices, así:

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

Esto debería hacer el truco:

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

Esta fue mi caso de uso, y me hubiera definido mi problema mucho de la misma manera que el OP (de hecho, una búsqueda de una frase similar me consiguió aquí), así que sospecho que esta respuesta puede ayudar a otros buscadores.

3

Muchas otras buenas respuestas. Sólo quería tirar éste en demasiado para Ruby 2.0 y 1.9.3:

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} 

Esto está apalancando: Hash::[] y Enumerable#flat_map

También en estas nuevas versiones hay Enumerable::each_with_object que es muy similar a Enumerable::inject/Enumerable::reduce :

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

Realizar una rápida benchmark (Rubí 2.0.0p0; 2012 Macbook Air) usando un hash original con 100 teclas, cada una con 100 valores distintos:

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 

muestra que la variante each_with_object es más rápida para ese conjunto de datos.

+0

gracias, Aaron, por enseñarme #flat_map –