Как объединить два хэша без новых ключей
Как я могу объединить два хэша, которые не приводят к новым ключам, что означает, что слияние будет объединять ключи, существующие в обеих хэшах?
Например, я хочу следующее:
h = {:foo => "bar"}
j = {:foo => "baz", :extra => "value"}
puts h.merge(j) # {:foo => "baz"}
Я ищу действительно чистый способ сделать это, так как моя текущая реализация довольно грязная.
Ответы
Ответ 1
Вы можете удалить ключи, которые не были в первом хеше со второго хеша, затем слить:
h.merge j.select { |k| h.keys.include? k }
В отличие от моей измененной альтернативы, это безопасно, если вы решите изменить ее на merge!
или update
.
Ответ 2
Если вы используете activesupport (часть рельсов), вы можете использовать 2 дополнительных метода на Hash
:
-
Hash#slice
принимает желаемые ключи как отдельные аргументы (а не массив ключей) и возвращает новый хэш только с запрошенными вами ключами.
-
Hash#except
принимает те же аргументы, что и slice
, но возвращает новый хеш с ключами, которые не были в аргументах.
Первая загрузка активных активов:
require 'active_support/core_ext'
Объединить только записи из j
, ключи которых уже находятся в h
(т.е. изменить, но не добавлять или удалять записи в h
):
h.merge(j.slice(*h.keys))
Пример:
ignore_new = ->(h, j) { h.merge(j.slice(* h.keys)) }
ignore_new.({a: 1, b: 2, c: 3}, {b: 10, c: 11, d: 12})
# => {:a=>1, :b=>10, :c=>11}
Получите остатки от j
, которые не были в h
:
j.except(*h.keys)
Bonus:
Если вы хотите истинное пересечение, то есть хотите получить результат, который имеет только общие ключи между двумя хэшами, выполните следующее:
h.merge(j).slice(* ( h.keys & j.keys ) )
Пример:
intersect = ->(h, j) { h.merge(j).slice(* (h.keys & j.keys) ) }
intersect.({a: 1, b: 2, c: 3}, {b: 10, c: 11, d: 12})
# => {:b=>10, :c=>11}
и остатки от h
, которые не были в j
:
h.except(*j.keys)
Вы также можете использовать activesupport HashWithIndifferentAccess
, если хотите, чтобы строка и ключ-доступ к символу считались эквивалентными.
Обратите внимание, что ни один из приведенных выше примеров не изменяет исходные хэши; вместо этого возвращаются новые хеши.
Ответ 3
Ответ Yjerem работает в Ruby 1.9, но не в 1.8.x. В 1.8.x метод Hash#select
возвращает массив. Hash#reject
возвращает хэш.
h.reject { |k,v| !j.keys.include? k }
Если вы хотите сохранить только пары ключ-значение, которые имеют одинаковые значения, вы можете сделать это:
h.reject { |k,v| j[k] != h[k] }
В краевом случае есть ниль. Если вы храните nils в своем Hash, вы должны это сделать:
h.reject { |k,v| !j.has_key? k or j[k] != h[k] }
Ответ 4
[h].inject({}) { |m,e| e.merge(j) { |k,o,n| m[k] = n }; m}
или
[{}].inject(h) { |m,e| m.merge(j) { |k,o,n| e[k] = n }; e}
или (возможно, лучшее, но не технически одно выражение)...
t = {}; h.merge(j) { |k,o,n| t[k] = n }; t
Ответ 5
Более индивидуальный способ сделать это:
h = {"foo"=> "bar"}
j = {"foo" => "baz", "extra" => "value"}
k = h.merge(j)
result: {"foo"=>"baz", "extra"=>"value"}
Здесь ключ "foo" во втором хэше переопределяет "foo" в первом хеше. Но если вы хотите сохранить старое значение i.e bar или хотите сохранить новое значение i.e "baz"? Вы можете сделать что-то вроде этого:
k = h.merge(j){|key, old, new| old}
result: {"foo"=>"bar", "extra"=>"value"}
k = h.merge(j){|key, old, new| new}
result: {"foo"=>"baz", "extra"=>"value"}