Recentemente, eu precisava de uma solução rápida para mesclar Hashes com esquemas diferentes, enquanto reduzia seus valores. No meu caso, tive uma grande matriz com muitos relatórios – em hashes – e precisei reduzir todos os seus valores em um relatório mesclado, mais ou menos assim:
hash_a = { { a: 1, b: 2, c: { d: 3, e: { f: 4, g: 5 } } } }
hash_b = { { a: 1, b: 2, c: { d: 3 } } }
reduced_hash = { { a: 2, b: 4, c: { d: 6, e: { f: 4, g: 5 } } } }
Como não consegui encontrar nada útil que resolvesse isso, escrevi o seguinte HashReducer para me ajudar (veja a essência ):
module HashReducer
# use the block to transform the addition/reduce result for each value
def self.reduce!(base: nil, add: nil, &block)
base = {} unless base
add = {} unless add
fail 'base: must be a hash!' unless base.is_a?(Hash)
fail 'add: must be a hash!' unless add.is_a?(Hash)
add.each do |k,v|
if v.is_a?(Hash)
base[k] = {} unless base[k]
base[k] = self.reduce!(base: base[k], add: v, &block)
else
initialize_value_field!(base, add, k)
base[k] += add[k] if add[k].respond_to?(:+)
if block_given?
base[k] = block.call(base[k])
end
end
end
base
end
private
def self.initialize_value_field!(base, add, k)
unless base[k]
if add[k].respond_to?(:new)
base[k] = add[k].class.new
elsif add[k].respond_to?(:+)
base[k] = 0
else
fail "Don't know how to initialize a #{add[k].class} object!"
end
end
end
end
O uso é simples, você precisa de um hash de base e um hash que deseja adicionar a ele:
base = {a: 1.99}
other = {a: 1, b: 2.99}
HashReducer.reduce!(base: base, add: other)
# {a: 2.99, b: 2.99}
Se você quiser fantasiar, pode transformar o resultado da soma passando um bloco:
HashReducer.reduce!(base: base, add: other) do |sum|
sum.round
end
# {a: 3, b: 3}
Se você souber de algo melhor, por favor me avise!