Как я могу объединить два хэша без перезаписи повторяющихся ключей в Ruby?

143

Есть ли простой или элегантный способ объединить два хэша без перезаписи повторяющихся ключей?

То есть, если ключ присутствует в исходном хэше, я не хочу менять его значение.

Клаудио Акчарези
источник
Вы действительно имеете в виду массивы (например: ['a', 'b', 'c']) или хеши (например: {'a' => 1, 'b' => 2, 'c' => 3}) ?
Alex Reisner
Извините, я говорил о
хешах

Ответы:

236

Если у вас есть два хэша, optionsи defaults, и вы хотите объединить defaultsих optionsбез перезаписи существующих ключей, то на самом деле вы хотите сделать обратное: объединить optionsв defaults:

options = defaults.merge(options)

Или, если вы используете Rails, вы можете:

options.reverse_merge!(defaults)
Алекс Рейснер
источник
Полностью согласен, большое спасибо за reverse_merge! метод не знал этого :)
Claudio Acciaresi
Зачем здесь нужны круглые скобки? Вы не можете просто сделать параметры default.merge, как он появляется.
Донато
1
Они устарели reverse_merge!из-за проблем с безопасностью в rails 5.1
Мирв - Мэтт
1
@ Mirv-Matt - я не вижу уведомления об амортизации. apidock.com/rails/v6.0.0/Hash/reverse_merge%21
Кшитидж
18

В стандартной библиотеке Ruby есть способ объединить хэши без перезаписи существующих значений или переназначения хэша.

important_hash.merge!(defaults) { |key, important, default| important }
ujifgc
источник
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-something» - вам может быть лучше просто добавить число к ключу, чтобы он стал key1, key2 и так далее, пока вы не нажмете тот, которого еще нет в hash1. Кроме того, я не использовал Ruby в течение нескольких месяцев, так что это, вероятно, синтаксически неверно, но вы должны быть в состоянии понять суть.

В качестве альтернативы переопределите значение ключа как массив, чтобы hash1 [key] возвращал исходное значение из hash1 и значение из hash2. Зависит от того, каким вы хотите добиться результата.

Glenatron
источник
Как насчет того, чтобы не сохранить оба ключа, а сложить значения одного и того же ключа?
Tom KC Chiu
1
@ TomK.C.Chiu Это будет во многом зависеть от обстоятельств, о которых мы не можем судить по вопросу - что, если значения в hash1 являются строками, а hash2 - целыми числами? В некоторых случаях это может быть жизнеспособным вариантом, но чаще вызывает проблемы - предложение использовать списки для значений работает довольно чисто.
glenatron
0

Здесь вы можете объединить свои 2 хэша reverse_merge

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

user = {
  name: 'Jhon Doe',
  email: 'jhon.doe@gmail.com' 
}
newHash = oder.reverse_merge!(user)
render json: { data: newHash, status: 200 }
Даниэль Агус Сидабутар
источник
0

Если вы хотите , чтобы объединить два хэшей optionsи defaultsбез перезаписи хэша назначения, вы можете проверить с , 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? })
Христианин
источник