Hoe sleutels en waarden in een hash om te wisselen

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 maplijkt 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 invertalle behalve de laatste instantie van uw waarden weggooien (omdat het de nieuwe waarde voor die sleutel blijft vervangen tijdens iteratie). Op dezelfde manier retourneert keyalleen 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#invertgebruiken. 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#inversegeeft 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 invertgewoon 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

Other episodes