如何在Ruby中合并两个哈希表,而不覆盖重复的键?

165

有没有一种简单或优雅的方法可以合并两个哈希表而不覆盖重复的键?

也就是说,如果原始哈希表中存在该键,我就不想改变它的值。


你是真的需要数组(例如:['a', 'b', 'c'])还是哈希(例如:{'a' => 1, 'b' => 2, 'c' => 3})? - Alex Reisner
抱歉,我说的是哈希值 :) - Claudio Acciaresi
5个回答

274
如果你有两个哈希表,optionsdefaults,并且你想将defaults合并到options中而不覆盖现有键,则你真正想做的是相反的操作:将options合并到defaults中。
options = defaults.merge(options)

或者,如果你正在使用Rails,你可以这样做:

options.reverse_merge!(defaults)

非常同意,非常感谢reverse_merge!我之前不知道它 :) - Claudio Acciaresi
为什么这里需要括号?似乎不能只使用default.merge选项。 - Donato
2
由于Rails 5.1中存在安全问题,他们正在弃用reverse_merge! - Mirv - Matt
1
@Mirv-Matt - 我没有看到任何关于折旧的通知。https://apidock.com/rails/v6.0.0/Hash/reverse_merge%21 - Kshitij
1
@Donato 在 Ruby 中的一个常见惯例是在返回值时使用括号,即使它们并不是严格必需的(很抱歉回答晚了7年)。 - mtjhax

23

在标准的Ruby库中有一种方法可以合并哈希,而不会覆盖现有的值或重新分配哈希。

important_hash.merge!(defaults) { |key, important, default| important }

3
如果您的问题是原始哈希值和第二个哈希值都可能有重复键,而您不想在任何方向上进行覆盖,那么您可能需要采用一种简单的手动合并方式,并进行某种冲突检查和处理:
hash2.each_key do |key|
  if ( hash1.has_key?(key) )
       hash1[ "hash2-originated-#{key}" ] = hash2[key]
  else
       hash1[key]=hash2[key]
  end
end

显然,这很基础,并且假设hash1没有任何名为“hash2-originated-whatever”的键 - 你最好只是向键添加数字,使其变成key1,key2等,直到找到一个不在hash1中的键。另外,我已经有几个月没有写过Ruby了,所以那可能不是语法正确的,但是你应该能够理解要点。
或者重新定义键的值为数组,这样hash1 [key]将返回来自hash1和hash2的原始值。这取决于你想要的结果是什么。

如果不保留两个键,而是将相同键的值相加,这样怎么样? - Tom K. C. Chiu
1
@TomK.C.Chiu 这将取决于我们无法从问题中判断的情况 - 如果hash1中的值是字符串而hash2中的值是整数怎么办?对于某些情况,这可能是可行的选择,但更常见的情况是会引起问题 - 使用列表作为值的建议可以很干净地解决这个问题。 - glenatron

1
在这里,您可以通过reverse_merge合并您的2个哈希。
order = {
 id: 33987,
 platform: 'web'
}

user = {
  name: 'Jhon Doe',
  email: 'jhon.doe@gmail.com' 
}
newHash = order.reverse_merge!(user)
render json: { data: newHash, status: 200 }

# => {:name=>"Jhon Doe", :email=>"jhon.doe@gmail.com", :id=>33987, :platform=>"web"}

你是如何让这个工作的?我在 Ruby 3.2.2 上尝试了一下,{}reverse_mergereverse_merge! 都没有响应,而且你的代码似乎没有包含 ActiveSupport。 - undefined

0
如果您想将两个哈希表 optionsdefaults 合并而不覆盖目标哈希表,可以使用 select 检查目标哈希表中是否已经存在该键。以下是纯 Ruby 解决方案(不含 Rails):
options  = { "a" => 100, "b" => 200 }
defaults = { "b" => 254, "c" => 300 }
options.merge!(defaults.select{ |k,_| not options.has_key? k })

# output
# => {"a"=>100, "b"=>200, "c"=>300}

或者,如果键存在但包含nil并且您想要覆盖它:

options.merge!(defaults.select{ |k,_| options[k].nil? })

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