2011-04-07 15 views
5

Tengo dos hashes que tienen una estructura algo similar a esto:La fusión de los hashes multidimensionales en Ruby

hash_a = { :a => { :b => { :c => "d" } } } 
hash_b = { :a => { :b => { :x => "y" } } } 

quiero fusionar estos juntos para producir la siguiente almohadilla:

{ :a => { :b => { :c => "d", :x => "y" } } } 

El La función de fusión reemplazará el valor de: a en el primer hash con el valor de: a en el segundo hash. Por lo tanto, escribí mi propia función de fusión recursiva, que se ve así:

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 

Pero consigo un error de ejecución: can't add a new key into hash during iteration. ¿Cuál es la mejor manera de fusionar estos hashes en Ruby?

Respuesta

6

Si cambia la primera línea de recursive_merge a

merged_hash = merge_to.clone 

funciona como se esperaba:

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

Cambio de la almohadilla, como Si avanzas es problemático, necesitas un "área de trabajo" para acumular tus resultados.

+0

Gracias por eso, pensé que era algo así de tonto. –

2

Pruebe esta solución mono-parcheo:

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 
+0

Funciona sin problemas, gracias! – OBCENEIKON

9

Ruby existente Hash#merge permite un formulario de bloque para resolver duplicados, por lo que es bastante simple. He agregado la funcionalidad para fusionar múltiples valores en conflicto en las 'hojas' de su árbol en una matriz; puedes elegir elegir uno o el otro en su lugar.

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

O, como una limpieza mono-patch:

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

"clean monkey-patch"? ¿No es eso un oxímoron? :-) –

+0

¡Gracias por esto! La solución perfecta para mi caso de uso con solo una pequeña modificación – aaaarrgh

7

usted puede hacerlo en una sola línea:

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

Si quieren imediatly merge el resultado en hash_a, justo reemplace el método merge por el método merge!

Si usted es usted cantar rieles o carriles 3 4 marco, es aún más fácil:

merged_hash = hash_a.deep_merge(hash_b) 

o

hash_a.deep_merge!(hash_b) 
+1

Para ruby ​​on rails esta es la respuesta correcta –

0

Con el fin de fusionar uno en el otro como el billete sugirió, podría modificar la función @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 

En caso de que haya duplicado de la llave, sólo se utilizará el contenido de merge_from de hash

0

Aquí es aún mejor solución para recursiva fusión que utiliza refinamientos y tiene método explosión junto con el apoyo bloque. Este código funciona en 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 

Después using HashRecursive fue ejecutado puede utilizar por defecto Hash::merge y Hash::merge! como si no se han modificado. Puede usar bloques con estos métodos como antes.

Lo nuevo es que puede pasar boolean recursive (segundo argumento) a estos métodos modificados y fusionarán los hashes recursivamente.


Ejemplo de uso para responder a la pregunta. Es muy fácil:

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

Para avanzaron ejemplo, echar un vistazo a this answer.

También eche un vistazo a mi versión recursiva de Hash::each (Hash::each_pair) here.

Cuestiones relacionadas