如何通过一组新的键来更改哈希表中的所有键?

49

如何通过一组新的键将哈希表中所有键替换为新的键?

有没有一种优雅的方式可以完成这个操作?

7个回答

79

假设你有一个将旧键映射到新键的Hash,你可以这样做:

hsh.transform_keys(&key_map.method(:[]))

4
你是个了不起的人!你的哈希值真的很棒!再次感谢你提供这个宝石! :) - CalebHC
1
它还假定您已经定义了key_map,它是一个键/值对哈希表,其中键是旧键,值是要交换的新键。 - the Tin Man
有人能解释一下 &key_map.method(:[]) 是如何工作的吗? - buncis

18

Ruby 2.5有Hash#transform_keys!方法。以下是使用键映射的示例:

h = {a: 1, b: 2, c: 3}
key_map = {a: 'A', b: 'B', c: 'C'}

h.transform_keys! {|k| key_map[k]}
# => {"A"=>1, "B"=>2, "C"=>3} 
您也可以使用symbol#toproc快捷方式,配合transform_keys使用。例如:
h.transform_keys! &:upcase
# => {"A"=>1, "B"=>2, "C"=>3}

4
我假设您想要更改哈希表中的键keys,而不更改值:
hash = {
   "nr"=>"123",
   "name"=>"Herrmann Hofreiter",
   "pferd"=>"010 000 777",
   "land"=>"hight land"
}
header = ["aa", "bb", "cc", "dd"]
new_hash = header.zip(hash.values).to_h

结果:

{
   "aa"=>"123",
   "bb"=>"Herrmann Hofreiter",
   "cc"=>"010 000 777",
   "dd"=>"high land"
}

1
稍微自我推广一下☺️,但只是为了留在这里:如果需要更复杂的东西(比如同时选择特定键或强制值类型等),我编写了一个小型库:https://github.com/smileart/hash_remapper - smileart
你利用哈希表保留顺序的事实(我认为自2.0开始)以及假设(我的问题中没有给出),即新键也按照初始哈希表中的顺序给出。 - JCLL

3
另一种方法是:
hash = {
  'foo' => 1,
  'bar' => 2
}

new_keys = {
  'foo' => 'foozle',
  'bar' => 'barzle'
}

new_keys.values.zip(hash.values_at(*new_keys.keys)).to_h 
# => {"foozle"=>1, "barzle"=>2}

拆分一下:

new_keys
.values # => ["foozle", "barzle"]
.zip(
  hash.values_at(*new_keys.keys) # => [1, 2]
) # => [["foozle", 1], ["barzle", 2]]
.to_h 
# => {"foozle"=>1, "barzle"=>2}

到了测试时间了...

虽然我喜欢Jörn的简洁回答,但我不确定它是否达到了应该有的速度,然后我看到了selvamani的评论:

require 'fruity'

HASH = {
  'foo' => 1,
  'bar' => 2
}

NEW_KEYS = {
  'foo' => 'foozle',
  'bar' => 'barzle'
}

compare do
  mittag    { HASH.dup.map {|k, v| [NEW_KEYS[k], v] }.to_h }
  ttm       { h = HASH.dup; NEW_KEYS.values.zip(h.values_at(*NEW_KEYS.keys)).to_h }
  selvamani { h = HASH.dup; h.keys.each { |key| h[NEW_KEYS[key]] = h.delete(key)}; h }
end

# >> Running each test 2048 times. Test will take about 1 second.
# >> selvamani is faster than ttm by 39.99999999999999% ± 10.0%
# >> ttm is faster than mittag by 10.000000000000009% ± 10.0%

这些选项的速度非常接近,因此任何一个都可以使用,但是39%的回报率随着时间的推移会更好,因此请考虑选择该选项。由于存在可能导致结果不佳的潜在缺陷,因此未包括其中的一些答案。


2

具体解决方案取决于您拥有新密钥的格式(或者是否可以从旧密钥派生新密钥)。

假设您有一个哈希表h,其键需要修改,以及一个将当前键映射到新键的哈希表new_keys,您可以执行以下操作:

h.keys.each do |key|
  h[new_keys[key]] = h[key] # add entry for new key
  k.delete(key)             # remove old key
end

1
new_keys意外地返回key本身时,这将删除key的值。barbolos在此问题的答案中克服了这个问题。 - sawa
3
你可以使用 h.keys.each { |key| h[new_keys[key]] = h.delete(key)} 替代此代码。 - Selvamani
@Selvamani,请查看我的回答,并从您的评论中创建一个答案。 - the Tin Man

0
h = { 'foo'=>1, 'bar'=>2 }
key_map = { 'foo'=>'foozle', 'bar'=>'barzle' }

h.each_with_object({}) { |(k,v),g| g[key_map[k]]=v }
  #=> {"foozle"=>1, "barzle"=>2}

或者

h.reduce({}) { |g,(k,v)| g.merge(key_map[k]=>v) }
  #=> {"foozle"=>1, "barzle"=>2} 

我认为为每个键调用合并函数会比较慢。 - Josh
@Josh,你说得对。我重新运行了@theTinMan的基准测试,并添加了我的两种方法,得到了以下结果:“selvamani比ttm快19.99% ± 1.0%; ttm与caryewo相似(使用each_with_object); caryewo与mittag相似; mittag比caryred(使用reduce)快70.0% ± 10.0%”。 - Cary Swoveland

0

如果 key_map[k] 为空,则会出现错误。 - the Tin Man

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接