Agrupe hashes por claves y sum los valores

Tengo una variedad de hashes:

[{"Vegetable"=>10}, {"Vegetable"=>5}, {"Dry Goods"=>3>}, {"Dry Goods"=>2}] 

Necesito inject aquí, creo, pero realmente he estado luchando.

Quiero un nuevo hash que refleje la sum de las claves duplicadas del hash anterior:

 [{"Vegetable"=>15}, {"Dry Goods"=>5}] 

Tengo el control del código que genera este hash para poder modificarlo si es necesario. Los resultados fueron principalmente hash, ya que esto podría terminar anidando cualquier cantidad de niveles de profundidad y luego es fácil invocar el aplanamiento en la matriz pero no aplanar las claves / valores del hash también:

 def recipe_pl(parent_percentage=nil) ingredients.collect do |i| recipe_total = i.recipe.recipeable.total_cost recipe_percentage = i.ingredient_cost / recipe_total if i.ingredientable.is_a?(Purchaseitem) if parent_percentage.nil? {i.ingredientable.plclass => recipe_percentage} else sub_percentage = recipe_percentage * parent_percentage {i.ingredientable.plclass => sub_percentage} end else i.ingredientable.recipe_pl(recipe_percentage) end end end 

 ar = [{"Vegetable"=>10}, {"Vegetable"=>5}, {"Dry Goods"=>3}, {"Dry Goods"=>2}] p ar.inject{|memo, el| memo.merge( el ){|k, old_v, new_v| old_v + new_v}} #=> {"Vegetable"=>15, "Dry Goods"=>5} 

Hash.merge con un bloque ejecuta el bloque cuando encuentra un duplicado; inject sin una memo inicial trata el primer elemento de la matriz como memo , lo cual está bien aquí.

Simplemente use:

 array = [{"Vegetable"=>10}, {"Vegetable"=>5}, {"Dry Goods"=>3}, {"Dry Goods"=>2}] array.inject{|a,b| a.merge(b){|_,x,y| x + y}} 
 ar = [{"Vegetable"=>10}, {"Vegetable"=>5}, {"Dry Goods"=>3}, {"Dry Goods"=>2}] 

Si bien la técnica Hash.merge funciona bien, creo que se lee mejor con una inject :

 ar.inject({}) { |memo, subhash| subhash.each { |prod, value| memo[prod] ||= 0 ; memo[prod] += value } ; memo } => {"Dry Goods"=>5, "Vegetable"=>15} 

Mejor aún, si usa Hash.new con un valor predeterminado de 0:

 ar.inject(Hash.new(0)) { |memo, subhash| subhash.each { |prod, value| memo[prod] += value } ; memo } => {"Dry Goods"=>5, "Vegetable"=>15} 

O si la inject hace doler la cabeza:

 result = Hash.new(0) ar.each { |subhash| subhash.each { |prod, value| result[prod] += value } } result => {"Dry Goods"=>5, "Vegetable"=>15} 

No estoy seguro de que un hash sea lo que quieras aquí, porque no tengo entradas múltiples en cada hash. entonces comenzaré cambiando un poco la representación de datos.

 ProductCount=Struct.new(:name,:count) data = [ProductCount.new("Vegetable",10), ProductCount.new("Vegetable",5), ProductCount.new("Dry Goods",3), ProductCount.new("Dry Goods",2)] 

Si los valores hash pueden tener múltiples pares clave-valor, entonces lo que probablemente quiera hacer es

 data = [{"Vegetable"=>10}, {"Vegetable"=>5}, {"Dry Goods"=>3>}, {"Dry Goods"=>2}] data = data.map{|h| h.map{|k,v| ProductCount.new(k,v)}}.flatten 

Ahora usa la gem de facetas de la siguiente manera

 require 'facets' data.group_by(&:name).update_values{|x| x.map(&:count).sum} 

El resultado es

 {"Dry Goods"=>5, "Vegetable"=>15} 

Si tiene dos valores hash con varias claves:

 h1 = { "Vegetable" => 10, "Dry Goods" => 2 } h2 = { "Dry Goods" => 3, "Vegetable" => 5 } details = {} (h1.keys | h2.keys).each do |key| details[key] = h1[key].to_i + h2[key].to_i end details