简体   繁体   English

通过多个键对哈希数组进行分组以在Ruby中找到平均值

[英]Group array of hashes by multiple keys to find average in Ruby

I have found solution for my problem done in JS but i need it done in Ruby (RoR). 我已经找到解决问题的解决方案,但是我需要在Ruby(RoR)中完成。 Here is link to problem and solution: Find average value for array of hashes using multiple group by 这是问题和解决方案的链接: 使用多个分组依据查找哈希数组的平均值

So i have array of hashes that needs to be grouped by keys (first subject_id then element_id ) and then find average values for them. 所以我有一个哈希数组,需要按键(首先subject_id然后element_id )进行分组,然后找到它们的平均值。 Number of hashes in array is not fixed. 数组中的哈希数不固定。

Below is input array: 下面是输入数组:

a=[
{:subject_id=>1, :element_id=>2, :value=>55},
{:subject_id=>1, :element_id=>4, :value=>33},
{:subject_id=>1, :element_id=>2, :value=>33},
{:subject_id=>1, :element_id=>4, :value=>1},
{:subject_id=>1, :element_id=>2, :value=>7},
{:subject_id=>1, :element_id=>4, :value=>4},
{:subject_id=>2, :element_id=>2, :value=>3},
{:subject_id=>2, :element_id=>2, :value=>5},
{:subject_id=>2, :element_id=>4, :value=>9}
]

Result: 结果:

b=[
{:subject_id=>1, :element_id=>2, :value=>95},
{:subject_id=>1, :element_id=>4, :value=>38},
{:subject_id=>2, :element_id=>2, :value=>8},
{:subject_id=>2, :element_id=>4, :value=>9}
]

I suggest that a counting hash be used to obtain the subtotals for the key :value and then construct the required of array of hashes from that hash. 我建议使用计数哈希来获取键:value的小计,然后从该哈希构造所需的哈希数组。 This uses the form of Hash#new that takes an argument that is the hash's default value. 这使用Hash#new的形式,该形式采用的参数是哈希的默认值。 That means that if a hash h does not have a key k , h[k] returns the default value. 这意味着,如果哈希h没有键k ,则h[k]返回默认值。

Computing totals 计算总数

a.each_with_object(Hash.new(0)) {|g,h| h[[g[:subject_id], g[:element_id]]] += g[:value]}.
  map {|(sub, el), tot| { subject_id: sub, element_id: el, value: tot}}
  #=> [{:subject_id=>1, :element_id=>2, :value=>95},
  #    {:subject_id=>1, :element_id=>4, :value=>38},
  #    {:subject_id=>2, :element_id=>2, :value=>8},
  #    {:subject_id=>2, :element_id=>4, :value=>9}]

Ruby, as a first step, unpacks the expression 第一步,将Ruby解压缩表达式

h[[g[:subject_id], g[:element_id]]] += g[:value]

changing it to 更改为

h[[g[:subject_id], g[:element_id]]] = h[[g[:subject_id], g[:element_id]]] + g[:value]

If h does not have a key [g[:subject_id], g[:element_id]] , h[[g[:subject_id], g[:element_id]]] on the right side of the equality returns the default value, 0 . 如果h没有等号右侧的键[g[:subject_id], g[:element_id]]h[[g[:subject_id], g[:element_id]]] ,则返回默认值0

Note that 注意

a.each_with_object(Hash.new(0)) {|g,h| h[[g[:subject_id], g[:element_id]]] += g[:value]}
  #=> {[1, 2]=>95, [1, 4]=>38, [2, 2]=>8, [2, 4]=>9}

Computing averages 计算平均值

Only a small change is needed to compute averages. 仅需很小的变化即可计算平均值。

a.each_with_object({}) do |g,h|
  pair = [g[:element_id], g[:subject_id]] 
  h[pair] = {tot: 0, count: 0} unless h.key?(pair)
  h[pair] = {tot: h[pair][:tot] + g[:value], count: h[pair][:count]+1}
end.map {|(sub, el),h| {subject_id: sub, element_id: el,
                        average: (h[:tot].to_f/h[:count]).round(1)}} 
  #=> [{:subject_id=>2, :element_id=>1, :average=>31.7},
  #    {:subject_id=>4, :element_id=>1, :average=>12.7},
  #    {:subject_id=>2, :element_id=>2, :average=>4.0},
  #    {:subject_id=>4, :element_id=>2, :average=>9.0}] 

Note 注意

a.each_with_object({}) do |g,h|
  pair = [g[:element_id], g[:subject_id]] 
  h[pair] = {tot: 0, count: 0} unless h.key?(pair)
  h[pair] = {tot: h[pair][:tot] + g[:value], count: h[pair][:count]+1}
end
  #=> {[2, 1]=>{:tot=>95, :count=>3}, [4, 1]=>{:tot=>38, :count=>3},
  #    [2, 2]=>{:tot=> 8, :count=>2}, [4, 2]=>{:tot=> 9, :count=>1}}

The result shown in the question isn't the average, it's the sum, so the result will be different: 问题中显示的结果不是平均值,而是总和,因此结果将有所不同:

  def groupByAndAverage(a)
    b = []

    a.each_with_index do |element, key|
      index = b.index do |x| 
        x != element && 
          x[:subject_id] == element[:subject_id] && 
            x[:element_id] == element[:element_id] 
      end
      if index
        b[index][:value] += element[:value]
        b[index][:amount] += 1
      else
        b.push a[key].merge(amount: 1)
      end

      true
    end
    b.map do |element| 
      element[:value] = element[:value] / element[:amount]
      element.delete(:amount)
      element
    end
    b
  end

And the result from this is: 结果是:

  [{:subject_id=>1, :element_id=>2, :value=>31}, 
    {:subject_id=>1, :element_id=>4, :value=>12}, 
    {:subject_id=>2, :element_id=>2, :value=>4}, 
    {:subject_id=>2, :element_id=>4, :value=>9}]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM