¿Cómo puedo combinar dos hashes sin sobrescribir las claves duplicadas en Ruby?

140

¿Existe una manera fácil o elegante de combinar dos hashes sin sobrescribir las claves duplicadas?

Es decir, si la clave está presente en el hash original, no quiero cambiar su valor.

Claudio Acciaresi
fuente
¿Realmente te refieres a matrices (por ejemplo: ['a', 'b', 'c']) o hashes (por ejemplo: {'a' => 1, 'b' => 2, 'c' => 3}) ?
Alex Reisner
Lo siento, estaba hablando de hashes :)
Claudio Acciaresi

Respuestas:

232

Si tiene dos valores hash, optionsy defaults, y desea combinar defaultsen optionssin sobrescribir las claves existentes, lo que realmente quiere hacer es a la inversa: la fusión optionsen defaults:

options = defaults.merge(options)

O, si está utilizando Rails, puede hacer:

options.reverse_merge!(defaults)
Alex Reisner
fuente
Totalmente de acuerdo, muchas gracias por reverse_merge! método no lo sabía :)
Claudio Acciaresi
¿Por qué se necesitan los paréntesis aquí? Simplemente no puede hacer default.merge opciones que aparece.
Donato
1
Están en desuso reverse_merge!debido a problemas de seguridad en los rieles 5.1
Mirv - Matt
@ Mirv-Matt: no veo un aviso de depreciación. apidock.com/rails/v6.0.0/Hash/reverse_merge%21
Kshitij
17

Hay una manera en la biblioteca estándar de Ruby para combinar Hashes sin sobrescribir los valores existentes o reasignar el hash.

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

Si su problema es que el hash original y el segundo pueden tener claves duplicadas y no desea sobrescribir en ninguna dirección, es posible que deba realizar una fusión manual simple con algún tipo de control y manejo de colisiones:

hash2.each_key do |key|
  if ( hash1.has_key?(key) )
       hash1[ "hash2-originated-#{key}" ] = hash2[key]
  else
       hash1[key]=hash2[key]
  end
end

Obviamente, esto es muy rudimentario y asume que hash1 no tiene ninguna tecla llamada "originada por hash2-lo que sea" - puede que sea mejor agregar un número a la tecla para que se convierta en clave1, clave2 y así sucesivamente hasta que presione uno que aún no está en hash1. Además, no he hecho ningún rubí en unos meses, por lo que probablemente no sea sintácticamente correcto, pero deberías poder entenderlo.

Alternativamente, redefina el valor de la clave como una matriz para que hash1 [clave] devuelva el valor original de hash1 y el valor de hash2. Depende de lo que quieras que sea realmente tu resultado.

glenatron
fuente
¿Qué tal si no mantiene ambas claves, sino que suma los valores de la misma clave?
Tom KC Chiu
1
@ TomK.C.Chiu Eso dependería mucho de las circunstancias que no podemos juzgar a partir de la pregunta: ¿qué sucede si los valores en hash1 son cadenas y hash2 son enteros? Para algunos casos, esa podría ser una opción viable, pero con mayor frecuencia causaría problemas: la sugerencia de usar listas para valores funciona de manera bastante clara.
glenatron
0

Aquí puedes combinar tus 2 hash por reverse_merge

order = {
 id: 33987,
 platform: 'web'
}

user = {
  name: 'Jhon Doe',
  email: '[email protected]' 
}
newHash = oder.reverse_merge!(user)
render json: { data: newHash, status: 200 }
Daniel Agus Sidabutar
fuente
0

Si desea fusionar los dos hash optionsy defaultssin sobrescribir el hash de destino, puede verificar selectsi la clave ya está presente en el hash de destino. Aquí está la solución Ruby pura sin rieles:

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}

O si la clave está presente, pero contiene nily desea sobrescribirla:

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