代码之家  ›  专栏  ›  技术社区  ›  TheNovice

将散列数组缩减为新的散列

  •  2
  • TheNovice  · 技术社区  · 6 年前

    我有一个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,
      }
    ]
    

    我想做的是创建一个与销售相关的所有键的“总和”(出于我们这里的目的,我不需要 timestamp user_id organization_id ,所以基本上,我想以这样的方式结束:

    {
      all_sales: 30
      direct_sales: 18
      referred_sales: 12
    }
    

    有没有优雅的红宝石风格的方法?我可以轻松地为每个销售类别创建一组变量,并在迭代原始关系时对它们进行扩充,但我想看看社区是否有更干净的方法。实际上,这些散列中的每一个都有远远超过3个相关键,因此我担心这种方法很快就会变得混乱。

    Better way to sum values in an array of hashes ),但理想情况下我不会重复这么多次。

    5 回复  |  直到 6 年前
        1
  •  5
  •   Marcin Kołodziej    6 年前

    这将起作用:

    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] 和最初一样 nil nil.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
  •  5
  •   codenamev    6 年前

    因为您是从ActiveRecord关系开始的,所以可以使用 pluck

    SalesModel.pluck('SUM(all_sales)', 'SUM(direct_sales)', 'SUM(referred_sales)')
    #=> [30, 18, 12]
    
        3
  •  2
  •   Fabio    6 年前

    或者使用函数方法 reduce merge 方法:

    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}
    

    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}
    
        4
  •  1
  •   Rajagopalan    6 年前

    使用归并归约函数

    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
    
        5
  •  1
  •   iGian    6 年前

    还有几个更详细的其他选项(可以呈现得更枯燥、更一般):

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