Cách trao đổi khóa và giá trị trong hàm băm


153

Làm cách nào để trao đổi khóa và giá trị trong Hash?

Tôi có Hash sau:

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

mà tôi muốn chuyển đổi thành:

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

Sử dụng mapcó vẻ khá tẻ nhạt. Có một giải pháp ngắn hơn?

Câu trả lời:


280

Ruby có một phương thức trợ giúp cho Hash cho phép bạn đối xử với Hash như thể nó được đảo ngược (về bản chất, bằng cách cho phép bạn truy cập các khóa thông qua các giá trị):

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

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

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

NHƯNG...

Nếu bạn có các giá trị trùng lặp, invertsẽ loại bỏ tất cả trừ lần xuất hiện cuối cùng của các giá trị của bạn (vì nó sẽ tiếp tục thay thế giá trị mới cho khóa đó trong quá trình lặp). Tương tự như vậy, keysẽ chỉ trả lại trận đấu đầ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, thì bạn có thể giữ tất cả các giá trị dưới dạng 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 hiện có trên GitHub .

Hoặc là:

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

4
each_with_objectcó ý nghĩa ở đây hơn inject.
Andrew Marshall

vì vậy mà trở nên each_with_object({}){ |i,o|k,v = *i; o[v] ||=[]; o[v] << k}... tốt đẹp
Nigel Thorne

3
Chúa ơi. tôi không biết bạn có thể làm | (khóa, giá trị), ra |. Điều đó thật tuyệt vời, tôi ghét mảng đó đến trong đó thay vì khóa và giá trị. Cảm ơn rất nhiều
Iuri G.

63

Bạn đặt cược có một! 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 voilà!


4
Đảo ngược Hash # không hoạt động nếu cùng một giá trị xuất hiện nhiều lần trong hàm băm của bạn.
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

Điều này sẽ xử lý các giá trị trùng lặp quá.


1
Bạn có thể giải thích câu trả lời những gì xảy ra trong mỗi bước không?
Sajjad Murtaza

Cá nhân tôi tránh thiết lập hành vi giá trị mặc định của hàm băm. Tôi lo lắng rằng bất cứ mã nào tôi đưa ra hàm băm này sẽ không mong muốn hàm băm hoạt động theo cách đó và nó có thể gây ra một số lỗi ngấm ngầm sau này. Tôi thực sự không thể biện minh cho mối quan tâm này. Đó chỉ là một nghi ngờ mà tôi dường như không thể bỏ qua. Nguyên tắc ít bất ngờ nhất?
Nigel Thorne

Khi trả lời bằng mã, điều thực sự quan trọng là giải thích cách mã hoạt động và tại sao đó là giải pháp phù hợp. Mục tiêu là để giáo dục, không chỉ đơn thuần là giải quyết vấn đề trước mắt.
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 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

trong khi invertphương thức tích hợp chỉ bị hỏng:

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

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

Nếu bạn có một hàm băm trong đó các 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 khóa không phải là duy nhất, tuy nhiên, trong đó chỉ các khóa cuối cùng được nhìn thấy sẽ được 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 hàm băm với các khóa không phải là 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 hàm 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"]}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.