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 map
có vẻ khá tẻ nhạt. Có một giải pháp ngắn hơn?
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 map
có vẻ khá tẻ nhạt. Có một giải pháp ngắn hơn?
Câu trả lời:
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, invert
sẽ 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, key
sẽ 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
each_with_object({}){ |i,o|k,v = *i; o[v] ||=[]; o[v] << k}
... tốt đẹp
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à!
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á.
# 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 invert
phương thức tích hợp chỉ bị hỏng:
h.invert
=> {1=>:a, 2=>:c} # FAIL
h.invert.invert == h
=> false # FAIL
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
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"]}
each_with_object
có ý nghĩa ở đây hơninject
.