Cómo intercambiar claves y valores en un hash


154

¿Cómo cambio claves y valores en un hash?

Tengo el siguiente Hash:

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

que quiero transformar en:

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

Usar mapparece bastante tedioso. ¿Hay una solución más corta?

Respuestas:


280

Ruby tiene un método auxiliar para Hash que le permite tratar un Hash como si estuviera invertido (en esencia, al permitirle acceder a las claves mediante valores):

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

Si desea mantener el hash invertido, 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, invertdescartará todos, excepto la última aparición de sus valores (porque seguirá reemplazando el nuevo valor para esa clave durante la iteración). Del mismo modo, keysolo devolverá el primer partido:

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

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

Entonces, si sus valores son únicos, puede usarlos Hash#invert. De lo contrario, puede mantener todos los valores como una matriz, como este:

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 con pruebas ahora está en GitHub .

O:

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

44
each_with_objecttiene más sentido aquí que inject.
Andrew Marshall

así que eso se vuelve each_with_object({}){ |i,o|k,v = *i; o[v] ||=[]; o[v] << k}... agradable
Nigel Thorne

3
Dios mio. no sabía que podía hacer | (clave, valor), fuera |. Eso es increíble, odiaba que esa matriz entrara allí en lugar de clave y valor. Muchas gracias
Iuri G.

63

¡Apuesto a que hay uno! ¡Siempre hay una forma más corta de hacer las cosas en Ruby!

Es bastante simple, solo usa Hash#invert:

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

Et voilà!


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

2
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

Esto también manejará los valores duplicados.


1
¿Puedes explicar un poco la respuesta que sucede en cada paso?
Sajjad Murtaza

Personalmente, evito establecer el comportamiento del valor predeterminado del hash. Me preocupa que cualquier código que le dé a este hash no espere que un hash se comporte de esa manera, y podría causar algún error insidioso más adelante. Realmente no puedo justificar esta preocupación. Es solo una duda que parece que no puedo ignorar. Principio de menor sorpresa?
Nigel Thorne

Al responder con código es realmente importante explicar cómo funciona el código y por qué es la solución adecuada. El objetivo es educar, no simplemente resolver el problema inmediato.
El hombre de hojalata

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 te dio:

 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 el invertmétodo incorporado está roto:

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

1

Usando 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

Si tiene un hash donde las claves son únicas, puede usar Hash # invert :

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

Sin embargo, eso no funcionará si tiene claves no únicas, donde solo se guardarán las últimas claves vistas:

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

Si tiene un hash con claves no únicas, puede 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 hacer:

> 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"]}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.