最有效的分组/汇总两个哈希的方法?

时间:2017-03-17 08:32:34

标签: ruby

我有两个哈希值,我需要聚合一些数据。第一个是ids(id_1,id_2,id_3,id_4)属于哪个类别(a,b,c)的映射:

hash_1 = {'a' => ['id_1','id_2'], 'b' => ['id_3'], 'c' => ['id_4']}

第二个哈希值包含给定日期(date_1,date_2,date_3)每个id发生的事件数量的值:

hash_2 = {
  'id_1' => {'date_1' => 5, 'date_2' => 6, 'date_3' => 8}, 
  'id_2' => {'date_1' => 0, 'date_3' => 6}, 
  'id_3' => {'date_1' => 0, 'date_2' => nil, 'date_3' => 1}, 
  'id_4' => {'date_1' => 10, 'date_2' => 1}
}

我想要的是获得每个类别的总事件(a,b,c)。对于上面的示例,结果将类似于:

hash_3 = {'a' => (5+6+8+0+6), 'b' => (0+0+1), 'c' => (10+1)}

我的问题是,大约有5000个类别,每个类别通常指向1到3个ID,每个ID具有30个或更多日期的事件计数。所以这需要相当多的计算。在Ruby中进行这种分组的最高性能(时间有效)方法是什么?

更新 这是我到目前为止所尝试的(花了6-8秒!,非常慢):

def total_clicks_per_category
  {}.tap do |res|
    hash_1.each do |cat, ids|
      res[cat] = total_event_per_ids(ids)
    end
  end
end

def total_event_per_ids(ids)
  ids.reduce(0) do |memo, id|
    events = hash_2.fetch(id, {})
    memo + (events.values.reduce(:+) || 0)
  end
end

P.S。我正在使用Ruby 2.3。

3 个答案:

答案 0 :(得分:3)

理论

5000*3*30并不是那么多。 Ruby可能需要一秒钟来完成这种工作。

默认情况下,哈希查找很快,您将无法进行太多优化。

您可以预先计算hash_2_sum

hash_2_sum = {
  'id_1' => 5+6+8, 
  'id_2' => 0+6, 
  'id_3' => 0+0+1, 
  'id_4' => 10+1
}

hash1上有hash_2_sum查询的循环,您就完成了。

代码

您的示例已使用某些nil值进行了更新。您需要使用compact删除它们,并确保在inject(0, :+)找不到任何元素时总和为0

hash_1 =  {'a' => ['id_1','id_2'], 'b' => ['id_3'], 'c' => ['id_4']}
hash_2 = {
  'id_1' => { 'date_1' => 5, 'date_2' => 6, 'date_3' => 8 },
  'id_2' => { 'date_1' => 0, 'date_3' => 6 },
  'id_3' => { 'date_1' => 0, 'date_2' => nil, 'date_3' => 1 },
  'id_4' => { 'date_1' => 10, 'date_2' => 1 }
}

hash_2_sum = hash_2.each_with_object({}) do |(key, dates), sum|
  sum[key] = dates.values.compact.inject(0, :+)
end

hash_3 = hash_1.each_with_object({}) do |(key, ids), sum|
  sum[key] = hash_2_sum.values_at(*ids).inject(0, :+)
end
# {"a"=>25, "b"=>1, "c"=>11}

注意

 {}.tap do |res|
    hash_1.each do |cat, ids|
      res[cat] = total_event_per_ids(ids)
    end
  end

不太可读恕我直言。

您可以使用each_with_objectArray#to_h

result = [1, 2, 3].each_with_object({}) do |i, hash|
  hash[i] = i * i
end
#=> {1=>1, 2=>4, 3=>9}

result = [1, 2, 3].map { |i| [i, i * i] }.to_h
#=> {1=>1, 2=>4, 3=>9}

答案 1 :(得分:3)

首先,创建一个包含hash_2

总和的中间哈希
hash_4 = hash_2.map{|k, v| [k, v.values.inject(:+)]}.to_h
# => {"id_1"=>19, "id_2"=>6, "id_3"=>1, "id_4"=>11}

然后做最后的总结:

hash_3 = hash_1.map{|k, v| [k, v.map{|k| hash_4[k]}.inject(:+)]}.to_h
# => {"a"=>25, "b"=>1, "c"=>11}

答案 2 :(得分:3)

我正在手机上写这个,所以我现在无法测试,但看起来还不错。

g = hash_2.each_with_object({}) { |(k,v),g| g[k] = v.values.compact.sum }
hash_3 = hash_1.each_with_object({}) { |(k,v),h| h[k] = g.values_at(*v).sum }