0

I have found solution for my problem done in JS but i need it done in 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. 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}
]

2 Answers 2

0

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}]
Sign up to request clarification or add additional context in comments.

Comments

0

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. This uses the form of Hash#new that takes an argument that is the hash's default value. That means that if a hash h does not have a key k, h[k] returns the default value.

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

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.

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

2 Comments

Thank you for help, i made a mistake in example where i showed sum as result instead of average.
I added the calculation of the averages.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.