Sum values in array of hash if they have the same value

一曲冷凌霜 提交于 2020-01-15 03:58:10

问题


I saw this piece of code in this post, because I'm trying to sum values in an array of hashes based on some criteria.

Rails sum values in an array of hashes

array = [
  {loading: 10, avg: 15, total: 25 },
  {loading: 20, avg: 20, total: 40 },
  {loading: 30, avg: 25, total: 55 }
]

sum = Hash.new(0)

array.each_with_object(sum) do |hash, sum|
  hash.each { |key, value| sum[key] += value }
end
 # => {:loading=>60, :avg=>60, :total=>120}

What I'm trying to do and I don't know how, is to sum total key if loading and avg appear with the same values more than once in this array. For instance.

 array = [
      {loading: 10, avg: 15, total: 25 },
      {loading: 20, avg: 20, total: 40 }, # See here
      {loading: 30, avg: 25, total: 55 },
      {loading: 20, avg: 20, total: 80 }, # See here
      {loading: 10, avg: 20, total: 46 }
    ]

The result would be:

[
  {loading: 10, avg: 15, total: 25 },
  {loading: 20, avg: 20, total: 120 }, # Results in this
  {loading: 30, avg: 25, total: 55 },
  {loading: 10, avg: 20, total: 46 }
]

I tried to modify this line

hash.each { |key, value| sum[key] += value }

Adding a conditional that checks if the value is repeated but I didn't succeed.

Any help, ideas or anything will be welcome.


回答1:


This seems work

array.group_by { |item| [item[:loading], item[:avg]] }.values.flat_map { |items| items.first.merge(total: items.sum { |h| h[:total] }) }
=> [{:loading=>10, :avg=>15, :total=>25}, {:loading=>20, :avg=>20, :total=>120}, {:loading=>30, :avg=>25, :total=>55}, {:loading=>10, :avg=>20, :total=>46}]



回答2:


You can use Enumerable#group_by and Hash#merge! to handle this as

array.group_by {|h| [h[:loading],h[:avg]]}.values.map do |a|
  a.inject do |memo,h| 
    memo.merge!(h) do |_k,old_value,new_value| 
      old_value + new_value
    end
  end
end
#=> [{:loading=>10, :avg=>15, :total=>25}, 
#    {:loading=>40, :avg=>40, :total=>120}, 
#    {:loading=>30, :avg=>25, :total=>55}, 
#    {:loading=>10, :avg=>20, :total=>46}]

Quick Breakdown

  • array.group_by group the elements by the loading and avg values this will return a hash of {grouped_values => [objects]}
  • values [objects] from the above groupings (because we don't specifically care about the keys)
  • map into a new Array
  • inject will pass the first element as memo and will yield the second element on the first iteration. After that memo will be the return value from inject and the next element will be yielded to the block until there are no elements left.
  • merge! with block syntax allows you to handle duplicate keys. In this case you want to add the values.



回答3:


This uses the form of Hash#update (aka merge!) that employs a block to determine the values of keys that are present in both hashes being merged.1 See the doc for the definitions of the block variables _k, o an n. The first variable (_k) begins with an underscore (often written simply _) to signify that that block variable is not used in the block calculation. (Note that @engineersmnky dd the same.)

arr = [
  {loading: 10, avg: 15, total: 25 },
  {loading: 20, avg: 20, total: 40 },
  {loading: 30, avg: 25, total: 55 },
  {loading: 20, avg: 20, total: 80 },
  {loading: 10, avg: 20, total: 46 }
]

arr.each_with_object({}) { |g,h| h.update(g.values_at(:loading, :avg)=>g) { |_k,o,n|
  o.merge(total: o[:total]+n[:total]) } }.values
  #=> [{:loading=>10, :avg=>15, :total=>25},
  #    {:loading=>20, :avg=>20, :total=>120},
  #    {:loading=>30, :avg=>25, :total=>55},
  #    {:loading=>10, :avg=>20, :total=>46}]

Before .values is applied at the end, we will have constructed the following hash:

arr.each_with_object({}) { |g,h| h.update(g.values_at(:loading, :avg)=>g) { |_k,o,n|
  o.merge(total: o[:total]+n[:total]) } }
  #=> {[10, 15]=>{:loading=>10, :avg=>15, :total=>25},
  #    [20, 20]=>{:loading=>20, :avg=>20, :total=>120},
  #    [30, 25]=>{:loading=>30, :avg=>25, :total=>55},
  #    [10, 20]=>{:loading=>10, :avg=>20, :total=>46}}

Had @Ursus (whose answer preceded mine) taken this approach I would have used group_by. The two approaches seems to always be interchangeable. I don't think one is better; it's just a matter of personal preference. It seems that @engineersmnky couldn't make up his mind, so he used a bit of both.

1. I feel like I've written this sentence (verbatim) hundreds of times. ¯\_(ツ)_/¯



来源:https://stackoverflow.com/questions/43876712/sum-values-in-array-of-hash-if-they-have-the-same-value

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