Как поменять ключи и значения в хэше?
У меня есть следующий хэш:
{:a=>:one, :b=>:two, :c=>:three}
что я хочу превратить в:
{:one=>:a, :two=>:b, :three=>:c}
Использование map
кажется довольно утомительным. Есть ли более короткое решение?
Как поменять ключи и значения в хэше?
У меня есть следующий хэш:
{:a=>:one, :b=>:two, :c=>:three}
что я хочу превратить в:
{:one=>:a, :two=>:b, :three=>:c}
Использование map
кажется довольно утомительным. Есть ли более короткое решение?
Ответы:
В Ruby есть вспомогательный метод для Hash, который позволяет вам обращаться с Hash, как если бы он был инвертирован (по сути, позволяя вам обращаться к ключам через значения):
{a: 1, b: 2, c: 3}.key(1)
=> :a
Если вы хотите сохранить инвертированный хеш, то Hash # invert должен работать в большинстве случаев:
{a: 1, b: 2, c: 3}.invert
=> {1=>:a, 2=>:b, 3=>:c}
НО...
Если у вас есть повторяющиеся значения, invert
все ваши значения будут исключены, кроме последнего (потому что он будет продолжать заменять новое значение для этого ключа во время итерации). Аналогично, key
вернется только первый матч:
{a: 1, b: 2, c: 2}.key(2)
=> :b
{a: 1, b: 2, c: 2}.invert
=> {1=>:a, 2=>:c}
Итак, если ваши значения уникальны, вы можете использовать Hash#invert
. Если нет, то вы можете сохранить все значения в виде массива, например так:
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
Примечание: этот код с тестами теперь на GitHub .
Или:
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}
... приятно
Вы держите пари, что есть один! В Ruby всегда есть более короткий путь!
Это довольно просто, просто используйте Hash#invert
:
{a: :one, b: :two, c: :three}.invert
=> {:one=>:a, :two=>:b, :three=>:c}
И вуаля!
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
Это также будет обрабатывать повторяющиеся значения.
# 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
дает тебе:
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
тогда как встроенный invert
метод просто сломан:
h.invert
=> {1=>:a, 2=>:c} # FAIL
h.invert.invert == h
=> false # FAIL
Использование массива
input = {:key1=>"value1", :key2=>"value2", :key3=>"value3", :key4=>"value4", :key5=>"value5"}
output = Hash[input.to_a.map{|m| m.reverse}]
Использование хэша
input = {:key1=>"value1", :key2=>"value2", :key3=>"value3", :key4=>"value4", :key5=>"value5"}
output = input.invert
Если у вас есть хеш, где ключи уникальны, вы можете использовать Hash # invert :
> {a: 1, b: 2, c: 3}.invert
=> {1=>:a, 2=>:b, 3=>:c}
Однако это не сработает, если у вас есть неуникальные ключи, где будут храниться только последние увиденные ключи:
> {a: 1, b: 2, c: 3, d: 3, e: 2, f: 1}.invert
=> {1=>:f, 2=>:e, 3=>:d}
Если у вас есть хеш с неуникальными ключами, вы можете сделать:
> 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]}
Если значения хэша уже являются массивами, вы можете сделать:
> 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
имеет больше смысла, чем здесьinject
.