如何对结构相同的Ruby嵌套哈希数组计算平均值?
问题
现有结构完全一致的嵌套哈希数组如下:
array = [ { "id": 8444, "version": "2.1.0", "data": { "data1": { "data1-1": { "a": 132.6, "b": 128.36, "c": 153.59, "d": 136.48 } }, "data2": { "data2-1": { "a": 1283.0, "b": 1254.0, "c": 1288.5, "d": 1329.0 } } } }, { "id": 8443, "version": "2.1.0", "data": { "data1": { "data1-1": { "a": 32.6, "b": 28.36, "c": 53.59, "d": 36.48 } }, "data2": { "data2-1": { "a": 283.0, "b": 254.0, "c": 288.5, "d": 329.0 } } } }, { "id": 8442, "version": "2.1.0", "data": { "data1": { "data1-1": { "a": 32.6, "b": 28.36, "c": 53.59, "d": 36.48 } }, "data2": { "data2-1": { "a": 283.0, "b": 254.0, "c": 288.5, "d": 329.0 } } } } ]
需要生成一个与原哈希中data结构完全相同的新哈希,其中a、b、c、d的取值为数组中对应位置所有值的平均值。
预期结果:
{ "data1": { "data1-1": { "a": 65.9, "b": 61.7, "c": 86.9, "d": 69.8 } }, "data2": { "data2-1": { "a": 616.3, "b": 587.3, "c": 621.8, "d": 662.3 } } }
尝试了以下代码,但仅保留了最后一个元素的data值,未实现累加平均:
array.reduce({}) do |acc, hash| hash[:data].each do |k,v| acc[k] = v end end # => {:data1=>{"data1-1"=>{:a=>32.6, :b=>28.36, :c=>53.59, :d=>36.48}}, # :data2=>{"data2-1"=>{:a=>283.0, :b=>254.0, :c=>288.5, :d=>329.0}}}
最优实现方案
由于数组中每个元素的data结构完全一致,核心思路是先累加所有对应位置的数值,再除以数组长度得到平均值。这里提供两种方案:
方案1:固定层级遍历(性能最优,适合已知结构)
如果哈希的嵌套层级固定,可以直接逐层遍历累加:
n = array.size # 初始化累加哈希,结构与第一个元素的data一致,所有数值设为0.0 sum_hash = Marshal.load(Marshal.dump(array.first[:data])).tap do |h| h[:data1][:"data1-1"].transform_values! { 0.0 } h[:data2][:"data2-1"].transform_values! { 0.0 } end # 遍历数组,累加所有对应位置的数值 array.each do |item| item[:data].each do |top_key, top_val| top_val.each do |sub_key, sub_val| sub_val.each do |k, v| sum_hash[top_key][sub_key][k] += v end end end end # 计算平均值得到最终结果 result = sum_hash.each_with_object({}) do |(top_key, top_val), acc| acc[top_key] = top_val.each_with_object({}) do |(sub_key, sub_val), sub_acc| sub_acc[sub_key] = sub_val.transform_values { |v| v / n } end end puts result.inspect
方案2:递归遍历(通用灵活,适配任意嵌套层级)
如果哈希的嵌套层级可能变化,递归方法更通用,无需硬编码层级:
# 递归累加嵌套哈希的对应值 def deep_sum(acc, current) current.each do |k, v| if v.is_a?(Hash) acc[k] ||= {} deep_sum(acc[k], v) else acc[k] ||= 0.0 acc[k] += v end end end # 递归计算平均值 def deep_average(sum_hash, n) sum_hash.each_with_object({}) do |(k, v), acc| acc[k] = v.is_a?(Hash) ? deep_average(v, n) : v / n end end n = array.size sum_hash = {} array.each { |item| deep_sum(sum_hash, item[:data]) } result = deep_average(sum_hash, n) puts result.inspect
错误代码分析
之前的reduce代码仅做了覆盖赋值:每次循环都用当前元素的data值替换累加器中的对应键,最终只保留了最后一个元素的data,完全没有进行数值累加,因此无法得到平均值。
内容的提问来源于stack exchange,提问作者SJU
相关产品推荐
相关产品推荐

