2012-06-12 31 views
118

En Ruby, ¿cómo puedo intercambiar claves y valores en un hash?Intercambio de claves y valores en un hash

Digamos que tengo el siguiente Hash:

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

que quiero transformar en:

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

Usar un mapa parece bastante tedioso. ¿Hay una solución más corta?

Respuesta

225

Ruby tiene un método de ayuda para el hash que permite tratar un hash como si se invierte.

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

Si desea mantener el hash invertida, entonces Hash#invert debería funcionar para la mayoría de las situaciones.

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

PERO ...

Si tiene valores duplicados, invert se descartando todos excepto el último de sus valores. Del mismo modo, key solo devolverá la primera coincidencia.

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

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

Así que .. si sus valores son únicos puede utilizar Hash#invert si no, entonces usted puede mantener todos los valores como una matriz, como esto:

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 

Nota: Este código es con las pruebas ahora here.

O en fin ...

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

¡Muy relevante! Gracias por señalar, no probé esos casos. –

+2

['each_with_object'] (http://ruby-doc.org/core-1.9.3/Enumerable.html#method-i-each_with_object) tiene más sentido aquí que' inject'. –

+0

de modo que se convierta en 'each_with_object ({}) {| i, o | k, v = * i; o [v] || = []; o [v] << k} '... agradable –

58

¡Usted apuesta que hay uno! ¡Siempre hay una manera más corta de hacer cosas en Ruby!

Es muy sencillo, sólo tiene que utilizar Hash#invert:

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

Et voilà!

+2

Hash # invertido no funciona si los mismos valores aparecen varias veces en su hash. – Tilo

1
# 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 le da:

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 

mientras que la incorporada en invert método que se acaba de romper:

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

Esta es de lejos la mejor respuesta. – kwerle

1

Uso Array

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

Usando Hash

input = {:key1=>"value1", :key2=>"value2", :key3=>"value3", :key4=>"value4", :key5=>"value5"} 
output = input.invert 
1
files = { 
    'Input.txt' => 'Randy', 
    'Code.py' => 'Stan', 
    'Output.txt' => 'Randy' 
} 

h = Hash.new{|h,k| h[k] = []} 
files.map {|k,v| h[v]<< k} 
puts h 

Este se encargará de los valores duplicados también.

0

Si usted tiene un hash dónde están las llaves son únicos, puede utilizar Hash#invert:

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

Eso no funcionará si tiene las claves no únicas, sin embargo, donde sólo los últimos teclas serán vistos mantienen:

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

Si usted tiene un hash con claves no únicas, que podría hacer:

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

Si los valores del hash ya son matrices, puede hacerlo:

> 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"]} 
Cuestiones relacionadas