2012-06-12 41 views
118

Trong Ruby, làm cách nào để trao đổi các khóa và giá trị trên một Hash?Trao đổi các khóa và giá trị trong một băm

Hãy nói rằng tôi có Hash sau:

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

Rằng tôi muốn biến đổi thành:

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

Sử dụng bản đồ có vẻ khá tẻ nhạt. Có giải pháp ngắn hơn không?

Trả lời

225

Ruby có phương thức trợ giúp cho hàm băm cho phép bạn xử lý băm như thể nó được đảo ngược.

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

Nếu bạn muốn giữ băm đảo ngược, thì Hash#invert sẽ hoạt động trong hầu hết các trường hợp.

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

NHƯNG ...

Nếu bạn có giá trị nhân bản, invert sẽ vứt bỏ tất cả, nhưng cuối cùng các giá trị của bạn. Tương tự như vậy key sẽ chỉ trả về kết quả trùng khớp đầu tiên.

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

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

Vì vậy .. nếu giá trị của bạn là duy nhất bạn có thể sử dụng Hash#invert nếu không, sau đó bạn có thể giữ tất cả các giá trị như một mảng, như thế này:

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 

Lưu ý: Mã này với các bài kiểm tra là bây giờ là here.

Hoặc trong ngắn hạn ...

class Hash 
    def safe_invert 
    self.each_with_object({}){|(k,v),o|(o[v]||=[])<<k} 
    end 
end 
+0

Rất có liên quan! Cảm ơn bạn đã chỉ ra, tôi đã không kiểm tra những trường hợp đó. –

+2

['each_with_object'] (http://ruby-doc.org/core-1.9.3/Enumerable.html#method-i-each_with_object) có ý nghĩa hơn ở đây so với' inject'. –

+0

để trở thành 'each_with_object ({}) {| i, o | k, v = * i; o [v] || = []; o [v] << k} '... đẹp –

58

Bạn đặt cược có một! Luôn luôn có một cách ngắn hơn để làm mọi thứ trong Ruby!

Nó khá đơn giản, chỉ cần sử dụng Hash#invert:

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

Et thì đấy!

+2

Hash # invert không hoạt động nếu cùng giá trị xuất hiện nhiều lần trong băm của bạn. – Tilo

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 mang đến cho bạn:

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 

khi được xây dựng trong invert phương pháp chỉ được chia:

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

Đây là câu trả lời hay nhất. – kwerle

1

Sử dụng Mảng

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

Sử dụng Hash

input = {:key1=>"value1", :key2=>"value2", :key3=>"value3", :key4=>"value4", :key5=>"value5"} 
output = input.invert 
1
files = { 
    'Input.txt' => 'Randy', 
    'Code.py' => 'Stan', 
    'Output.txt' => 'Randy' 
} 

h = Hash.new{|h,k| h[k] = []} 
files.map {|k,v| h[v]<< k} 
puts h 

này sẽ xử lý các giá trị nhân bản quá.

0

Nếu bạn có một băm mà là chìa khóa là duy nhất, bạn có thể sử dụng Hash#invert:

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

Điều đó sẽ không hoạt động nếu bạn có các phím không độc đáo, tuy nhiên, nơi chỉ có các phím nhìn thấy lần cuối sẽ giữ:

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

Nếu bạn có một băm với các phím không duy nhất, bạn có thể làm:

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

Nếu các giá trị của băm đã là mảng, bạn có thể làm:

> 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"]} 
Các vấn đề liên quan