Hoe wissel ik sleutels en waarden in een hash?
Ik heb de volgende hash:
{:a=>:one, :b=>:two, :c=>:three}
die ik wil transformeren in:
{:one=>:a, :two=>:b, :three=>:c}
Het gebruik van map
lijkt nogal vervelend. Is er een kortere oplossing?
Antwoord 1, autoriteit 100%
Ruby heeft een hulpmethode voor hash waarmee u een hash kunt behandelen alsof deze omgekeerd is (in wezen door u toegang te geven tot sleutels via waarden):
{a: 1, b: 2, c: 3}.key(1)
=> :a
Als je de omgekeerde hash wilt behouden, gebruik dan Hash#invertzou in de meeste situaties moeten werken:
{a: 1, b: 2, c: 3}.invert
=> {1=>:a, 2=>:b, 3=>:c}
MAAR…
Als u dubbele waarden heeft, zal invert
alle behalve de laatste instantie van uw waarden weggooien (omdat het de nieuwe waarde voor die sleutel blijft vervangen tijdens iteratie). Op dezelfde manier retourneert key
alleen de eerste overeenkomst:
{a: 1, b: 2, c: 2}.key(2)
=> :b
{a: 1, b: 2, c: 2}.invert
=> {1=>:a, 2=>:c}
Dus als uw waarden uniek zijn, kunt u Hash#invert
gebruiken. Zo niet, dan kunt u alle waarden als een array behouden, zoals dit:
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
Opmerking: deze code met tests staat nu op GitHub.
Of:
class Hash
def safe_invert
self.each_with_object({}){|(k,v),o|(o[v]||=[])<<k}
end
end
Antwoord 2, autoriteit 21%
Je wed dat er een is! Er is altijd een kortere manier om dingen te doen in Ruby!
Het is vrij eenvoudig, gebruik gewoon Hash#invert
:
{a: :one, b: :two, c: :three}.invert
=> {:one=>:a, :two=>:b, :three=>:c}
Et voilà!
Antwoord 3
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
Dit zal ook de dubbele waarden afhandelen.
Antwoord 4
Als je een hash hebt waar de sleutels uniek zijn, kun je Hash#invert:
> {a: 1, b: 2, c: 3}.invert
=> {1=>:a, 2=>:b, 3=>:c}
Dat werkt echter niet als u niet-unieke sleutels heeft, waarbij alleen de laatst gevonden sleutels worden bewaard:
> {a: 1, b: 2, c: 3, d: 3, e: 2, f: 1}.invert
=> {1=>:f, 2=>:e, 3=>:d}
Als u een hash heeft met niet-unieke sleutels, kunt u het volgende doen:
> 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]}
Als de waarden van de hash al arrays zijn, kun je het volgende doen:
> 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"]}
Antwoord 5
# 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
geeft je:
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
terwijl de ingebouwde methode invert
gewoon niet werkt:
h.invert
=> {1=>:a, 2=>:c} # FAIL
h.invert.invert == h
=> false # FAIL
Antwoord 6
Array gebruiken
input = {:key1=>"value1", :key2=>"value2", :key3=>"value3", :key4=>"value4", :key5=>"value5"}
output = Hash[input.to_a.map{|m| m.reverse}]
Hash gebruiken
input = {:key1=>"value1", :key2=>"value2", :key3=>"value3", :key4=>"value4", :key5=>"value5"}
output = input.invert