Group hashes by keys and sum the values

为君一笑 提交于 2019-11-26 09:08:06

问题


I have an array of hashes:

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

I need to use inject here I think but I\'ve really been struggling.

I want a new hash that reflects the sum of the previous hash\'s duplicate keys:

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

I\'m in control of the code that outputs this hash so I can modify it if necessary. The results were mainly hashes because this could end up nested any number of levels deep and then it\'s easy to call flatten on the array but not flatten the keys/values of the hash too:

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 

回答1:


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 with a block runs the block when it finds a duplicate; inject without a initial memo treats the first element of the array as memo, which is fine here.




回答2:


Simply use:

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



回答3:


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

While the Hash.merge technique works fine, I think it reads better with an inject:

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

Better yet, if you use Hash.new with a default value of 0:

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

Or if inject makes your head hurt:

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



回答4:


I'm not sure that a hash is what you want here, because I don't multiple entries in each hash. so I'll start by changing your data representation a little.

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

If the hashes can have multiple key-value pairs, then what you probably want to do is

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

Now use the facets gem as follows

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

The result is

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



回答5:


If have two hashes with multiple keys:

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


来源:https://stackoverflow.com/questions/4453511/group-hashes-by-keys-and-sum-the-values

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!