在哈希数组中对属性求和并对它们进行分组

时间:2018-03-23 12:17:11

标签: ruby

我有一个这样的数组:

res = [
  {:partner_name=>"company 1", :partner_id=>787, :value=>1}, 
  {:partner_name=>"company 2", :partner_id=>768, :value=>1},
  {:partner_name=>"company 3", :partner_id=>769, :value=>1},
  {:partner_name=>"company 1", :partner_id=>787, :value=>2}
]

我尝试做的是创建一个数组,该数组将保存每partner_id个值的总和。例如,上面的输出将是:

[{:partner_name=>"company 1", :partner_id=>787, :value=>3}, 
 {:partner_name=>"company 2", :partner_id=>768, :value=>1},
 {:partner_name=>"company 3", :partner_id=>769, :value=>1}]

试图玩弄它:

res.each do |r|
  if hash.key?(r[:partner_id])
    hash[:value] += r[:value]
  else
    hash = r
  end
end

有了这个以及其他几次尝试,无法让它发挥作用。

4 个答案:

答案 0 :(得分:2)

以下代码有效。基本上,有两个步骤:

按照partner_id对哈希进行分组;使用value求和来加入这些群组。

arr = [{ partner_name: "company 1", partner_id: 787, value: 1 }, 
       { partner_name: "company 2", partner_id: 768, value: 1},
       { partner_name: "company 3", partner_id: 769, value: 1},
       { partner_name: "company 1", partner_id: 787, value: 2}]

arr.group_by { |hash| hash[:partner_id] }.map do |_k, values| 
  { partner_name: values.first[:partner_name], 
    partner_id: values.first[:partner_id], 
    value: values.sum { |val| val[:value] } }
end

或者还有下面这些内容,但它并没有很好地阅读,但却使用了merge&#39}的块arg:

arr.group_by { |hash| hash[:partner_id] }.map do |_k, values| 
  values.reduce({}) do |a, e| 
    a.merge(e) do |key, old_val, new_val| 
      key == :value ? old_val += new_val : old_val
    end
  end
end

让我知道你如何继续这些!

答案 1 :(得分:1)

试试这个

arr.group_by { |item| item[:partner_id] }.transform_values do |items| 
  items_values_sum = items.sum { |item| item[:value] }
  items.first.merge(value: items_values_sum) 
end.values

transform_values很酷但是我们来自ruby 2.4.0,否则使用map作为@SRack指出

答案 2 :(得分:0)

这很简短,很好地强调了问题的Map / Reduce本质:

arr.group_by{|e| e[:partner_id]}.map do |_,v|
  v.reduce{|r,e| r.merge! value: r[:value].to_i + e[:value] }
end

答案 3 :(得分:0)

您的问题涉及Ruby应用程序中非常常见的操作。对于这种问题,总有两种可用的方法。第一种是采用方法Enumerable#group_by

使用group_by

hash = res.group_by { |h| h[:partner_id] }
  #=> {787=>[{:partner_name=>"company 1", :partner_id=>787, :value=>1},
  #          {:partner_name=>"company 1", :partner_id=>787, :value=>2}],
  #    768=>[{:partner_name=>"company 2", :partner_id=>768, :value=>1}],
  #    769=>[{:partner_name=>"company 3", :partner_id=>769, :value=>1}]}

我们现在希望构造一个包含三个元素的数组,每个元素对应一个键值对hash。该数组的第一个元素,由

构成
hash[787]
  #=> [{:partner_name=>"company 1", :partner_id=>787, :value=>1},
  #    {:partner_name=>"company 1", :partner_id=>787, :value=>2}]

{:partner_name=>"company 1", :partner_id=>787, :value=>3}

当我们将hash的每个键值对转换为其他东西(哈希)时,应该想到方法Enumerable#map。这是进行转换的一种方法。

hash.map do |k,v|
  # obtain the sum of the values of :value over each element (hash) of v
  tot = v.sum { |h| h[:value] }
  # merge { :value=>tot } into any element of `v` (say, v.first)
  v.first.merge(:value=>tot)
end
  #=> [{:partner_name=>"company 1", :partner_id=>787, :value=>3},
  #    {:partner_name=>"company 2", :partner_id=>768, :value=>1},
  #    {:partner_name=>"company 3", :partner_id=>769, :value=>1}]

v.first.merge(:value=>tot)v.first.merge( {:value=>tot} )常用的快捷方式。

其他答案显示了用较少的陈述来做到这一点的方法,但基本思想是相同的。顺便说一句,Enumerable#sum在Ruby v2.4中首次亮相。要支持旧版本,请使用Enumerable#reduce(又名inject)。

tot = v.reduce(0) { |t,h| t + h[:value] }

使用Hash#update(又名merge!)的形式使用块来确定合并的两个哈希中存在的键的值。

使用这种方法,我们将构造一个哈希hash,其键是:partners_id的不同值,但与group_by不同,其值是反映总数的所需哈希值:value的给定键的键hash。完成后,我们只返回哈希值hash

hash = res.each_with_object({}) do |g,h|
  h.update(g[:partner_id]=>g) {|k,o,n| o.merge(:value=>o[:value]+n[:value])}        
end
  #=> {787=>{:partner_name=>"company 1", :partner_id=>787, :value=>3},
  #    768=>{:partner_name=>"company 2", :partner_id=>768, :value=>1},
  #    769=>{:partner_name=>"company 3", :partner_id=>769, :value=>1}}

数组hash的值因此提供了所需的返回值。

hash.values
  #=> [{:partner_name=>"company 1", :partner_id=>787, :value=>3},
  #    {:partner_name=>"company 2", :partner_id=>768, :value=>1},
  #    {:partner_name=>"company 3", :partner_id=>769, :value=>1}]

让我们看一下hash是如何构建的。

我们首先计算一个枚举器。

enum = res.each_with_object({})
  #=> #<Enumerator: [
  #     {:partner_name=>"company 1", :partner_id=>787, :value=>1},
  #     {:partner_name=>"company 2", :partner_id=>768, :value=>1},
  #     {:partner_name=>"company 3", :partner_id=>769, :value=>1},
  #     {:partner_name=>"company 1", :partner_id=>787, :value=>2}
  #   ]:each_with_object({})>

请注意

hash == enum.each do |g,h|
  h.update(g[:partner_id]=>g) {|k,o,n| o.merge(:value=>o[:value]+n[:value])}
end
  #=> true

Enumerator#each原因生成枚举数的第一个值并将其传递给块,并使用消歧将值分配给块变量(有时称为解构 EM>)。

g,h = enum.next
  #=> [{:partner_name=>"company 1", :partner_id=>787, :value=>1}, {}]
g #=> {:partner_name=>"company 1", :partner_id=>787, :value=>1}
h #=> {}

Enumerator#next。我们现在可以执行块计算。

h.update(g[:partner_id]=>g) {|k,o,n| o.merge(:value=>o[:value]+n[:value])}
  #=> h.update(787=>{:partner_name=>"company 1", :partner_id=>787, :value=>1})
  #=> {787=>{:partner_name=>"company 1", :partner_id=>787, :value=>1}}

由于h为空(无密钥),因此将散列{ 787=>{:partner_name=>"company 1", :partner_id=>787, :value=>1} }合并到其中需要查询update块,因为没有密钥存在于两个哈希都是merged!。将enum的下两个值中的每一个传递给块后,情况也是如此。

g,h = enum.next
  #=> [{:partner_name=>"company 2", :partner_id=>768, :value=>1},
  #    {787=>{:partner_name=>"company 1", :partner_id=>787, :value=>1}}]
g #=> {:partner_name=>"company 2", :partner_id=>768, :value=>1}
h #=> {787=>{:partner_name=>"company 1", :partner_id=>787, :value=>1}}
h.update(g[:partner_id]=>g)
  #=> {787=>{:partner_name=>"company 1", :partner_id=>787, :value=>1},
  #    768=>{:partner_name=>"company 2", :partner_id=>768, :value=>1}}

g,h = enum.next
  #=> [{:partner_name=>"company 3", :partner_id=>769, :value=>1},
  #    {787=>{:partner_name=>"company 1", :partner_id=>787, :value=>1},
  #     768=>{:partner_name=>"company 2", :partner_id=>768, :value=>1}}]
g #=> {:partner_name=>"company 3", :partner_id=>769, :value=>1}
h #=> {787=>{:partner_name=>"company 1", :partner_id=>787, :value=>1},
  #    768=>{:partner_name=>"company 2", :partner_id=>768, :value=>1}}
h.update(g[:partner_id]=>g)
  #=> {787=>{:partner_name=>"company 1", :partner_id=>787, :value=>1},
  #    768=>{:partner_name=>"company 2", :partner_id=>768, :value=>1},
  #    769=>{:partner_name=>"company 3", :partner_id=>769, :value=>1}}

注意每一步如何更新h。当enum的最终元素生成并传递给块时,事情会发生变化。

g,h = enum.next
  #=> [{:partner_name=>"company 1", :partner_id=>787, :value=>2},
  #    {787=>{:partner_name=>"company 1", :partner_id=>787, :value=>1},
  #     768=>{:partner_name=>"company 2", :partner_id=>768, :value=>1},
  #     769=>{:partner_name=>"company 3", :partner_id=>769, :value=>1}}]
g #=> {:partner_name=>"company 1", :partner_id=>787, :value=>2}
h #=> {787=>{:partner_name=>"company 1", :partner_id=>787, :value=>1},
  #    768=>{:partner_name=>"company 2", :partner_id=>768, :value=>1},
  #    769=>{:partner_name=>"company 3", :partner_id=>769, :value=>1}}
h.update(g[:partner_id]=>g) {|k,o,n| o.merge(:value=>o[:value]+n[:value])}
  #=> {787=>{:partner_name=>"company 1", :partner_id=>787, :value=>3},
  #    768=>{:partner_name=>"company 2", :partner_id=>768, :value=>1},
  #    769=>{:partner_name=>"company 3", :partner_id=>769, :value=>1}}

{ g[:partner_id]=>g} #=> { 787=>g }合并到h后,我们发现两个哈希值都有公共密钥787。因此,我们遵循该块来确定合并散列中787的值。三个块变量的值如下。

k = 787    # the common key
o = h[787] # the "old" value of k 
n = g      # the "new" value of k

请注意

o[:value] #=> 1
n[:value] #=> 2

因此块计算

o.merge(:value=>o[:value]+n[:value])
  #=> h[787].merge( { :value=>1+2 }
  #=> {:partner_name=>"company 1", :partner_id=>787, :valu} 

The current value of {ħ{1}} hash`。