So tauschen Sie Schlüssel und Werte in einem Hash aus

153

Wie tausche ich Schlüssel und Werte in einem Hash aus?

Ich habe folgenden Hash:

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

dass ich mich verwandeln möchte in:

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

Die Verwendung mapscheint ziemlich mühsam. Gibt es eine kürzere Lösung?

Jonathan Allard
quelle

Antworten:

280

Ruby verfügt über eine Hilfsmethode für Hash, mit der Sie einen Hash so behandeln können, als wäre er invertiert (im Wesentlichen, indem Sie über Werte auf Schlüssel zugreifen können):

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

Wenn Sie den invertierten Hash beibehalten möchten, sollte Hash # invert in den meisten Situationen funktionieren:

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

ABER...

Wenn Sie doppelte Werte haben, invertwerden alle bis auf das letzte Auftreten Ihrer Werte verworfen (da während der Iteration immer wieder neue Werte für diesen Schlüssel ersetzt werden). Ebenso keywird nur das erste Spiel zurückgegeben:

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

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

Wenn Ihre Werte eindeutig sind, können Sie sie verwenden Hash#invert. Wenn nicht, können Sie alle Werte wie folgt als Array beibehalten:

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

Hinweis: Dieser Code mit Tests ist jetzt auf GitHub .

Oder:

class Hash
  def safe_invert
    self.each_with_object({}){|(k,v),o|(o[v]||=[])<<k}
  end
end
Nigel Thorne
quelle
4
each_with_objectmacht hier mehr Sinn als inject.
Andrew Marshall
so dass each_with_object({}){ |i,o|k,v = *i; o[v] ||=[]; o[v] << k}... schön wird
Nigel Thorne
3
Oh mein Gott. Ich wusste nicht, dass Sie | (Schlüssel, Wert), out | tun können. Das war so großartig, dass ich es hasste, dass dieses Array anstelle von Schlüssel und Wert hereinkam. Vielen Dank
Iuri G.
63

Wetten, dass es einen gibt? In Ruby gibt es immer einen kürzeren Weg!

Es ist ziemlich einfach, benutze einfach Hash#invert:

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

Et voilà!

Jonathan Allard
quelle
4
Hash # invert funktioniert nicht, wenn dieselben Werte in Ihrem Hash mehrmals vorkommen.
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

Dies behandelt auch die doppelten Werte.

Riaze
quelle
1
Können Sie die Antwort erklären, was in jedem Schritt passiert?
Sajjad Murtaza
Persönlich vermeide ich es, das Standardwertverhalten des Hash festzulegen. Ich mache mir Sorgen, dass jeder Code, dem ich diesen Hash gebe, nicht erwarten würde, dass sich ein Hash so verhält, und dass dies später zu einem heimtückischen Fehler führen könnte. Ich kann diese Sorge nicht wirklich rechtfertigen. Es ist nur ein Zweifel, den ich nicht ignorieren kann. Prinzip der geringsten Überraschung?
Nigel Thorne
Bei der Beantwortung mit Code ist es sehr wichtig zu erklären, wie der Code funktioniert und warum er die richtige Lösung ist. Ziel ist es, das unmittelbare Problem zu erziehen und nicht nur zu lösen.
Der Blechmann
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 gibt Ihnen:

 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

während die eingebaute invertMethode nur kaputt ist:

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

Array verwenden

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

Verwenden von Hash

input = {:key1=>"value1", :key2=>"value2", :key3=>"value3", :key4=>"value4", :key5=>"value5"}
output = input.invert
Prashant Ravi Darshan
quelle
1

Wenn Sie einen Hash haben, bei dem die Schlüssel eindeutig sind, können Sie Hash # invert verwenden :

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

Dies funktioniert jedoch nicht, wenn Sie nicht eindeutige Schlüssel haben, bei denen nur die zuletzt gesehenen Schlüssel aufbewahrt werden:

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

Wenn Sie einen Hash mit nicht eindeutigen Schlüsseln haben, können Sie Folgendes tun:

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

Wenn die Werte des Hashs bereits Arrays sind, können Sie Folgendes tun:

> 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"]}
dawg
quelle