Jak zamienić klucze i wartości w skrócie

154

Jak zamienić klucze i wartości w skrócie?

Mam następujący hash:

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

który chcę przekształcić w:

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

Używanie mapwydaje się dość żmudne. Czy istnieje krótsze rozwiązanie?

Jonathan Allard
źródło

Odpowiedzi:

280

Ruby ma pomocniczą metodę dla Hash, która pozwala traktować Hash tak, jakby był odwrócony (w istocie, umożliwiając dostęp do kluczy poprzez wartości):

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

Jeśli chcesz zachować odwrócony hash, to Hash # invert powinno działać w większości sytuacji:

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

ALE...

Jeśli masz zduplikowane wartości, invertspowoduje odrzucenie wszystkich wystąpień wartości oprócz ostatniego (ponieważ będzie zastępować nową wartość dla tego klucza podczas iteracji). Podobnie keyzwróci tylko pierwsze dopasowanie:

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

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

Jeśli więc twoje wartości są wyjątkowe, możesz użyć Hash#invert. Jeśli nie, możesz zachować wszystkie wartości jako tablicę, na przykład:

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

Uwaga: ten kod z testami jest teraz na GitHub .

Lub:

class Hash
  def safe_invert
    self.each_with_object({}){|(k,v),o|(o[v]||=[])<<k}
  end
end
Nigel Thorne
źródło
4
each_with_objectma więcej sensu niż inject.
Andrew Marshall,
więc to staje się each_with_object({}){ |i,o|k,v = *i; o[v] ||=[]; o[v] << k}... miłe
Nigel Thorne
3
O mój Boże. nie wiedziałem, że możesz zrobić | (klucz, wartość), wyjście |. To takie niesamowite, nienawidziłem tej tablicy zamiast klucza i wartości. Wielkie dzięki
Iuri G.
63

Pewnie, że jest jeden! W Rubim zawsze jest krótszy sposób robienia rzeczy!

To całkiem proste, po prostu użyj Hash#invert:

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

Zrobione!

Jonathan Allard
źródło
4
Hash # invert nie działa, jeśli te same wartości pojawiają się wiele razy w haszu.
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

Spowoduje to również obsługę zduplikowanych wartości.

Riaze
źródło
1
Czy możesz trochę wyjaśnić odpowiedź, co dzieje się na każdym kroku?
Sajjad Murtaza
Osobiście unikam ustawiania domyślnego zachowania wartości skrótu. Martwię się, że jakikolwiek kod, któremu podam ten hash, nie spodziewałby się, że hash będzie zachowywał się w ten sposób, i może później spowodować podstępny błąd. Naprawdę nie mogę usprawiedliwić tej troski. To tylko wątpliwość, której nie mogę zignorować. Zasada najmniejszego zaskoczenia?
Nigel Thorne
Odpowiadając za pomocą kodu, bardzo ważne jest wyjaśnienie, jak działa kod i dlaczego jest to odpowiednie rozwiązanie. Celem jest edukacja, a nie tylko rozwiązywanie bezpośredniego problemu.
The Tin Man
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 daje Ci:

 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

podczas gdy wbudowana invertmetoda jest po prostu zepsuta:

 h.invert
  => {1=>:a, 2=>:c}    # FAIL
 h.invert.invert == h 
  => false             # FAIL
Tilo
źródło
1

Korzystanie z Array

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

Korzystanie z skrótu

input = {:key1=>"value1", :key2=>"value2", :key3=>"value3", :key4=>"value4", :key5=>"value5"}
output = input.invert
Prashant Ravi Darshan
źródło
1

Jeśli masz hash, w którym klucze są unikalne, możesz użyć Hash # invert :

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

To nie zadziała, jeśli masz nieunikalne klucze, ale tylko ostatnie widoczne klucze zostaną zachowane:

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

Jeśli masz skrót z nieunikalnymi kluczami, możesz zrobić:

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

Jeśli wartości skrótu są już tablicami, możesz:

> 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"]}
psie
źródło