¿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 map
parece bastante tedioso. ¿Hay una solución más corta?
¿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 map
parece bastante tedioso. ¿Hay una solución más corta?
Respuestas:
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, invert
descartará 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, key
solo 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
each_with_object({}){ |i,o|k,v = *i; o[v] ||=[]; o[v] << k}
... agradable
¡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à!
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.
# 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 invert
método incorporado está roto:
h.invert
=> {1=>:a, 2=>:c} # FAIL
h.invert.invert == h
=> false # FAIL
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
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"]}
each_with_object
tiene más sentido aquí queinject
.