2011-04-07 80 views
5

多維哈希我有有一個類似的結構,東西兩個散列:合併在Ruby中

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中合併這些哈希的最佳方式是什麼?如果更改recursive_merge的第一線,

merged_hash = merge_to.clone 

它按預期工作

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 

回答

6

recursive_merge(hash_a, hash_b)  
-> {:a=>{:b=>{:c=>"d", :x=>"y"}}} 

改變哈希作爲

+0

謝謝你,我認爲這是一件愚蠢的事情。 – 2011-04-07 13:24:15

2

試試這個猴子補丁解決方案你通過它是很麻煩的,你需要一個「工作區」來積累你的結果。

+0

完美無瑕地工作,謝謝! – OBCENEIKON 2017-03-09 18:34:40

9

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"}}} 
+3

「乾淨的猴子補丁」?這不是一個矛盾嗎? :-) – 2011-04-07 14:40:02

+0

非常感謝!我的使用案例只有一個微小的修改完美的解決方案 – aaaarrgh 2017-05-17 20:59:01

7

您可以在一行做到這一點:

merged_hash = hash_a.merge(hash_b){|k,hha,hhb| hha.merge(hhb){|l,hhha,hhhb| hhha.merge(hhhb)}} 

如果你想imediatly merge結果爲hash_a,只是替換方法合併的方法merge!

如果你是你唱軌道3或軌道4框架,它是更容易:

merged_hash = hash_a.deep_merge(hash_b) 

hash_a.deep_merge!(hash_b) 
+1

對於軌道上的紅寶石,這是正確的答案 – 2015-02-25 13:26:19

0

爲了合併成一個作爲門票建議等,你可以修改@Phrogz功能

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哈希的內容

0

這裏是遞歸合併使用改進並具有爆炸方法一起與塊支持更好的解決方案。此代碼在pure 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::mergeHash::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" } } } 

對於先進例子看看this answer

也看看我的遞歸版本Hash::eachHash::each_pairhere