ruby - create - Agrupe hashes por claves y suma los valores
key value ruby (5)
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 gema 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}
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 suma 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
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
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}
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í.