Ruby 对散列数组中的属性求和并将其分组

Ruby 对散列数组中的属性求和并将其分组,ruby,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=>

我有这样一个数组:

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\u 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

通过此操作和其他几次尝试,无法使其正常工作。

以下代码正常工作。基本上有两个步骤:

按其
合作伙伴id
对哈希进行分组;用
求和来加入这些组

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
的block 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
让我知道你是怎么处理这些的

试试这个

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\u values
很酷,但我们从ruby 2.4.0开始,否则使用
map
,正如@SRack指出的那样这很简短,很好地强调了问题的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

您的问题涉及Ruby应用程序中的一个非常常见的操作。对于这类问题,通常可以采取两种方法。首先是采用这种方法

使用
分组依据

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[787]
  #=> [{:partner_name=>"company 1", :partner_id=>787, :value=>1},
  #    {:partner_name=>"company 1", :partner_id=>787, :value=>2}]

当我们将
散列的每个键值对转换为其他内容(散列)时,应该想到该方法。这里有一种方法可以实现这种转换

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})
常用的快捷方式

其他答案用较少的陈述说明了实现这一点的方法,但基本思想是相同的。顺便提一下,它在Ruby v2.4中首次亮相。要支持旧版本,请使用(aka
inject

使用(aka
merge!
)的形式,使用一个块来确定合并的两个散列中存在的键的值。

通过这种方法,我们将构造一个散列
散列
,其键是
:partners\u id
的不同值,但与
group\u 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.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}]
让我们看看
散列
是如何构造的

我们首先计算一个枚举数

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({})>
导致生成枚举数的第一个值并将其传递给块,并使用消歧(有时称为解构)将值分配给块变量

看。我们现在可以执行块计算

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\u name=>“company 1”,“partner\u id=>787,:value=>1}}
合并到它中确实需要查询
update
的块,因为合并的两个散列中都没有键。将
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}}
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}}
注意
h
在每个步骤中是如何更新的。当生成
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}}
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.merge(:value=>o[:value]+n[:value])
  #=> h[787].merge( { :value=>1+2 }
  #=> {:partner_name=>"company 1", :partner_id=>787, :valu} 

返回
h
的当前值,结束
hash`.

Nice的构造。不知道
转换\u值
+1New stuff bro:d考虑到提问者可能对Ruby很陌生,你认为你已经为你的答案提供了充分的解释吗?我相信你不会挖掘我的小编辑。通过将变量(
res
)分配给哈希数组,读者可以在注释和答案中引用该变量,而无需定义它。对于SO问题中给出的所有示例中的所有输入对象,都应该这样做。这对@Mongoid有帮助吗?
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}