我有两个哈希值,我需要聚合一些数据。第一个是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。
答案 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_object
或Array#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 }