【问题标题】:Sum values in array of hash if they have the same value如果它们具有相同的值,则对哈希数组中的值求和
【发布时间】:2017-06-14 09:03:05
【问题描述】:

我在这篇文章中看到了这段代码,因为我正在尝试根据某些标准对哈希数组中的值求和。

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}

我正在尝试做但我不知道怎么做,如果加载和平均在这个数组中多次出现相同的值,则对总键求和。例如。

 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 }
    ]

结果是:

[
  {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 }
]

我试图修改这一行

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

添加一个条件来检查值是否重复但我没有成功。

欢迎任何帮助、想法或任何事情。

【问题讨论】:

  • 我希望您认为在 ** 中包含一些哈希值会对读者有所帮助,但我只是觉得它令人困惑。如果没有“嘿,看这里”,你想做什么就很清楚了。更一般地说,在给出示例时,所有输入都应该是有效的 Ruby 对象(没有**,没有etc.,没有...)。除其他外,它允许读者简单地剪切和粘贴您的代码,而无需进行后续编辑。值得称赞的是,您为数组分配了一个变量(array = [...),因此读者可以参考array 而无需定义它。许多提问者将输入无名,这是一个小麻烦。

标签: arrays ruby hash


【解决方案1】:

这似乎可行

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】:

您可以使用Enumerable#group_byHash#merge! 来处理这个问题

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}]

快速分解

  • array.group_by 按 loading 和 avg 值对元素进行分组,这将返回 {grouped_values => [objects]}
  • 的哈希值
  • values [objects] 来自上述分组(因为我们并不特别关心键)
  • map 到一个新的数组中
  • inject 将第一个元素作为备忘录传递,并在第一次迭代时产生第二个元素。之后,备忘录将是注入的返回值,并且下一个元素将被让出到块中,直到没有剩余元素为止。
  • merge! 带有块语法允许您处理重复的键。在这种情况下,您要添加值。

【讨论】:

    【解决方案3】:

    这使用Hash#update(又名merge!)的形式,它使用一个块来确定在被合并的两个哈希中存在的键的值。1有关定义,请参见文档块变量_kon。第一个变量(_k)以下划线开头(通常简称为_),表示该块变量未用于块计算。 (注意@engineersmnky dd 相同。)

    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}]
    

    在最后应用.values之前,我们将构建以下哈希:

    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}}
    

    如果@Ursus(他的回答在我之前)采用这种方法,我会使用group_by。这两种方法似乎总是可以互换的。我不认为一个更好。这只是个人喜好问题。 @engineersmnky 似乎无法下定决心,所以他两者都用了一点。

    1.我觉得我已经把这句话(逐字)写了几百遍了。 ¯\_(ツ)_/¯

    【讨论】:

    • 哎哟。 :P 我下定决心并选择了可读性。 “当然,人们确实是双向的。这就是麻烦。我拿不定主意。”
    猜你喜欢
    • 2014-11-21
    • 2020-06-12
    • 2022-01-21
    • 2011-04-13
    • 1970-01-01
    • 1970-01-01
    • 2017-04-05
    • 2013-02-24
    相关资源
    最近更新 更多