将哈希数组减少为新哈希

时间:2018-10-05 23:13:15

标签: ruby

我有一个ActiveRecord关系,看起来像这样:

[  
  {  
    timestamp: Tue, 02 Oct 2018 00:00:00 PDT -07:00,
    user_id: 3,
    organization_id: 1,
    all_sales: 10,
    direct_sales: 7,
    referred_sales: 3,
  },
  {
    timestamp: Wed, 03 Oct 2018 00:00:00 PDT -07:00,
    user_id: 3,
    organization_id: 1,
    all_sales: 17,
    direct_sales: 8,
    referred_sales: 9,
  },  
  {
    timestamp: Thu, 04 Oct 2018 00:00:00 PDT -07:00,
    user_id: 3,
    all_sales: 3,
    direct_sales: 3,
    referred_sales: 0,
  }
]

我想做的是为与销售有关的所有密钥创建一个“和”(出于我们此处的目的,我不需要timestampuser_id或{{1} },所以基本上,我想以这样的结尾:

organization_id

是否有一种优雅的红宝石色方式?我可以轻松地为每个销售类别创建一组变量,并在迭代原始关系时增加它们,但是我想看看社区中是否有更清洁的方法。实际上,这些散列中的每个散列都具有超过3个相关的键,因此,我担心这种方法会很快变得混乱。

编辑:我还在SO上检查了类似问题的其他答案(例如:Better way to sum values in an array of hashes),但理想情况下,我不会重复那么多次。

5 个答案:

答案 0 :(得分:5)

由于您是从ActiveRecord关系开始的,因此可以使用pluck用SQL计算所有总和,并让它返回一个包含总数的数组:

SalesModel.pluck('SUM(all_sales)', 'SUM(direct_sales)', 'SUM(referred_sales)')
#=> [30, 18, 12]

答案 1 :(得分:4)

这将起作用:

arr.each_with_object({}) do |obj, hash|
  %i[all_sales direct_sales referred_sales].each do |sym|
    hash[sym] = hash[sym].to_i + obj[sym]
  end
end

这是一次迭代,您可以将嵌套循环写成3条不同的行,但是我认为这样比较干净。

注意:在获取to_i的先前值的同时调用hash[sym],最初的值为nilnil.to_i == 0。另外,您可以将所有未知计数初始化为0,如下所示:

arr.each_with_object(Hash.new(0)) do |obj, hash|
  %i[all_sales direct_sales referred_sales].each do |sym|
    hash[sym] += obj[sym]
  end
end

答案 2 :(得分:2)

或将功能性方法与reducemerge方法一起使用:

keys = %i{all_sales direct_sales referred_sales}
total_sales = items.map {|item| item.select{|key, _| keys.include?(key)}}
                   .reduce({}) {|all, item| all.merge(item) {|_, sum, value| sum + value}}

# total_sales
# => {:all_sales=>30, :direct_sales=>18, :referred_sales=>12}

感谢@Johan Wentholt,对于Ruby 2.5.0或更高版本,几乎没有更清晰的出价方法

items.map {|item| item.slice(:all_sales, :direct_sales, :referred_sales)}
     .reduce({}) {|all, item| all.merge(item) {|_, sum, value| sum + value}}

# => {:all_sales=>30, :direct_sales=>18, :referred_sales=>12}

答案 3 :(得分:1)

使用合并和归约功能

value = arr.reduce do |h1, h2|
  h1.merge(h2) do |k, v1, v2|
    [:all_sales, :direct_sales, :referred_sales].include?(k) ? (v1 + v2) : nil
  end
end.reject {|_, v| v.nil?}

p value

答案 4 :(得分:1)

更多详细的其他选项(可以呈现为更DRY和更常规):

result = { all_sales: (array.sum{ |e| e[:all_sales] }), direct_sales: (array.sum{ |e| e[:direct_sales] }), referred_sales: (array.sum{ |e| e[:referred_sales] }) }

或:

result = array.each.with_object(Hash.new(0)) do |h, obj|
  obj[:all_sales] += h[:all_sales]
  obj[:direct_sales] += h[:direct_sales]
  obj[:referred_sales] += h[:referred_sales]
end

要更加干燥和通用,请从所需键的数组开始

keys = [:all_sales, :direct_sales, :referred_sales]

第一个成为

keys.map.with_object(Hash.new) { |k, obj| obj[k] = array.sum { |e| e[k] } }

第二个:

array.each.with_object(Hash.new(0)) { |h, obj| keys.each { |k| obj[k] += h[k] } }