Cara menukar kunci dan nilai dalam hash

154

Bagaimana cara menukar kunci dan nilai dalam Hash?

Saya memiliki Hash berikut:

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

yang ingin saya ubah menjadi:

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

Penggunaannya mapagak membosankan. Apakah ada solusi yang lebih pendek?

Jonathan Allard
sumber

Jawaban:

280

Ruby memiliki metode pembantu untuk Hash yang memungkinkan Anda memperlakukan Hash seolah-olah itu terbalik (pada dasarnya, dengan membiarkan Anda mengakses kunci melalui nilai-nilai):

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

Jika Anda ingin menyimpan hash terbalik, maka Hash # invert harus berfungsi untuk sebagian besar situasi:

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

TAPI...

Jika Anda memiliki nilai duplikat, invertakan membuang semua kecuali kemunculan nilai terakhir Anda (karena itu akan terus mengganti nilai baru untuk kunci itu selama iterasi). Demikian juga, keyhanya akan mengembalikan pertandingan pertama:

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

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

Jadi, jika nilai Anda unik, Anda dapat menggunakannya Hash#invert. Jika tidak, maka Anda dapat menyimpan semua nilai sebagai array, seperti ini:

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

Catatan: Kode dengan tes ini sekarang ada di GitHub .

Atau:

class Hash
  def safe_invert
    self.each_with_object({}){|(k,v),o|(o[v]||=[])<<k}
  end
end
Nigel Thorne
sumber
4
each_with_objectlebih masuk akal di sini daripada inject.
Andrew Marshall
jadi itu menjadi each_with_object({}){ |i,o|k,v = *i; o[v] ||=[]; o[v] << k}... bagus
Nigel Thorne
3
Oh Tuhan. saya tidak tahu Anda bisa melakukan | (kunci, nilai), keluar |. Itu sangat luar biasa, saya benci array yang datang ke sana daripada kunci dan nilai. Terima kasih banyak
Iuri G.
63

Anda bertaruh ada satu! Selalu ada cara yang lebih singkat untuk melakukan sesuatu di Ruby!

Ini sangat sederhana, cukup gunakan Hash#invert:

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

Dan lagi!

Jonathan Allard
sumber
4
Hash # invert tidak berfungsi jika nilai yang sama muncul beberapa kali dalam hash Anda.
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

Ini akan menangani nilai duplikat juga.

Riaze
sumber
1
Bisakah Anda sedikit menjelaskan jawabannya apa yang terjadi di setiap langkah?
Sajjad Murtaza
Secara pribadi saya menghindari pengaturan perilaku nilai default hash. Saya khawatir bahwa kode apa pun yang saya berikan hash ini tidak akan mengharapkan hash untuk berperilaku seperti itu, dan itu bisa menyebabkan beberapa kesalahan berbahaya nantinya. Saya tidak bisa benar-benar membenarkan masalah ini. Sepertinya aku tidak bisa mengabaikannya. Prinsip kejutan paling tidak?
Nigel Thorne
Ketika menjawab dengan kode, sangat penting untuk menjelaskan cara kerja kode dan mengapa itu solusi yang tepat. Tujuannya adalah untuk mendidik, bukan hanya menyelesaikan masalah langsung.
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 Memberi anda:

 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

sedangkan invertmetode bawaan hanya rusak:

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

Menggunakan Array

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

Menggunakan Hash

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

Jika Anda memiliki hash di mana kuncinya unik, Anda dapat menggunakan Hash # invert :

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

Namun, itu tidak akan berhasil jika Anda memiliki kunci yang tidak unik, di mana hanya kunci terakhir yang terlihat yang akan disimpan:

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

Jika Anda memiliki hash dengan kunci tidak unik, Anda dapat melakukannya:

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

Jika nilai hash sudah array, Anda bisa melakukan:

> 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
sumber