我有两个散列,它们的结构类似于:
hash_a = { :a => { :b => { :c => "d" } } }
hash_b = { :a => { :b => { :x => "y" } } }
我想把这些合并在一起产生下面的哈希:
{ :a => { :b => { :c => "d", :x => "y" } } }
合并函数会将第一个哈希值中的:a替换为第二个哈希值中的:a。所以,我写了我自己的递归归并函数,看起来像这样:
def recursive_merge( merge_from, merge_to )
merged_hash = merge_to
first_key = merge_from.keys[0]
if merge_to.has_key?(first_key)
merged_hash[first_key] = recursive_merge( merge_from[first_key], merge_to[first_key] )
else
merged_hash[first_key] = merge_from[first_key]
end
merged_hash
end
但是我得到一个运行时错误:can't add a new key into hash during iteration
。在Ruby中合并这些哈希最好的方法是什么?
Ruby现有的Hash#merge
允许块形式来解析重复项,这使得它相当简单。我已经添加了合并多个冲突的值在你的树的"叶子"到一个数组的功能;你可以选择其中之一。
hash_a = { :a => { :b => { :c => "d", :z => 'foo' } } }
hash_b = { :a => { :b => { :x => "y", :z => 'bar' } } }
def recurse_merge(a,b)
a.merge(b) do |_,x,y|
(x.is_a?(Hash) && y.is_a?(Hash)) ? recurse_merge(x,y) : [*x,*y]
end
end
p recurse_merge( hash_a, hash_b )
#=> {:a=>{:b=>{:c=>"d", :z=>["foo", "bar"], :x=>"y"}}}
或者,作为一个干净的猴子补丁:
class Hash
def merge_recursive(o)
merge(o) do |_,x,y|
if x.respond_to?(:merge_recursive) && y.is_a?(Hash)
x.merge_recursive(y)
else
[*x,*y]
end
end
end
end
p hash_a.merge_recursive hash_b
#=> {:a=>{:b=>{:c=>"d", :z=>["foo", "bar"], :x=>"y"}}}
可以一行完成:
merged_hash = hash_a.merge(hash_b){|k,hha,hhb| hha.merge(hhb){|l,hhha,hhhb| hhha.merge(hhhb)}}
如果您想立即将结果merge
转换为hash_a,只需将方法merge替换为方法merge!
如果您正在使用rails 3或rails 4框架,则更容易:
merged_hash = hash_a.deep_merge(hash_b)
或
hash_a.deep_merge!(hash_b)
如果将recursive_merge的第一行更改为
merged_hash = merge_to.clone
按预期运行:
recursive_merge(hash_a, hash_b)
-> {:a=>{:b=>{:c=>"d", :x=>"y"}}}
在你移动的时候改变哈希值是很麻烦的,你需要一个"工作区"来积累你的结果。
试试这个猴子补丁解决方案:
class Hash
def recursive_merge(hash = nil)
return self unless hash.is_a?(Hash)
base = self
hash.each do |key, v|
if base[key].is_a?(Hash) && hash[key].is_a?(Hash)
base[key].recursive_merge(hash[key])
else
base[key]= hash[key]
end
end
base
end
end
为了将一个合并到另一个,您可以修改@Phrogz function
def recurse_merge( merge_from, merge_to )
merge_from.merge(merge_to) do |_,x,y|
(x.is_a?(Hash) && y.is_a?(Hash)) ? recurse_merge(x,y) : x
end
end
如果有重复键,它将只使用merge_from
散列的内容
这里有一个更好的递归合并的解决方案,它使用改进,并具有bang方法以及块支持。这段代码在纯 Ruby上工作。
module HashRecursive
refine Hash do
def merge(other_hash, recursive=false, &block)
if recursive
block_actual = Proc.new {|key, oldval, newval|
newval = block.call(key, oldval, newval) if block_given?
[oldval, newval].all? {|v| v.is_a?(Hash)} ? oldval.merge(newval, &block_actual) : newval
}
self.merge(other_hash, &block_actual)
else
super(other_hash, &block)
end
end
def merge!(other_hash, recursive=false, &block)
if recursive
self.replace(self.merge(other_hash, recursive, &block))
else
super(other_hash, &block)
end
end
end
end
using HashRecursive
执行using HashRecursive
后,您可以使用默认的Hash::merge
和Hash::merge!
,就好像它们没有被修改过一样。您可以像以前一样使用块和这些方法。
新的东西是,你可以传递布尔值recursive
(第二个参数)给这些修改过的方法,它们将递归地合并哈希。
示例用于回答问题。这非常简单:
hash_a = { :a => { :b => { :c => "d" } } }
hash_b = { :a => { :b => { :x => "y" } } }
puts hash_a.merge(hash_b) # Won't override hash_a
# output: { :a => { :b => { :x => "y" } } }
puts hash_a # hash_a is unchanged
# output: { :a => { :b => { :c => "d" } } }
hash_a.merge!(hash_b, recursive=true) # Will override hash_a
puts hash_a # hash_a was changed
# output: { :a => { :b => { :c => "d", :x => "y" } } }
关于高级的例子,看看这个答案。
还可以看看我的递归版本的Hash::each
(Hash::each_pair
)在这里。