4

I am trying to create an array/hash from an array of multiple hashes with same keys and an average of values. My array:

[{:amount=>897500, :gross_amount=>897500, :tax=>147500, :hotel_fees=>0, :base_fare=>750000, :currency=>"INR"}, {:amount=>1006500, :gross_amount=>1006500, :tax=>156500, :hotel_fees=>0, :base_fare=>850000, :currency=>"INR"}]

Now I want to return something like this:

{:amount=>952000, :gross_amount=>952000, :tax=>152000, :hotel_fees=>0, :base_fare=>800000, :currency=>"INR"}

where values are the average of values from each hash with same key.

Is there a simple way to do this. I have tried using merge but currency becomes 0 with it.

My attempt:

p[0].merge(p[1]){|k,v1,v2| (v1+v2)/2 unless v1 && v2 == "INR"}

Edit:

Actually my problem didn't end here, so after getting the average I needed to insert the values inside another hash. So I used something like this:

        price_array = offer_values.map do |v| 
          v.inject do |k, v| 
            k.merge!(price: k[:price].merge(v[:price]){|_, a, b| [a, b].flatten })
          end
        end
        price_array.map do |o|
          o[:price] = {}.tap{ |h| o[:price].each {|k, list| h[k] = list.all?{|e| [Fixnum, NilClass].include? e.class} ? list.map(&:to_i).sum/list.size : list.compact.first ; h  } }
        end

Where offer_array is the one with my orginal/first array in separate hashes. This I have tried for with 2 and 3 hashes and it is working.

If you guys have any suggestion on improving the code, It am open.

5
  • 1
    "I have tried using merge" – could you show your attempt? Commented Jun 13, 2017 at 15:53
  • Merge returns this {:amount=>952000, :gross_amount=>952000, :tax=>152000, :hotel_fees=>0, :base_fare=>800000, :currency=>nil} Commented Jun 13, 2017 at 15:54
  • I can't have the currency nil. Commented Jun 13, 2017 at 15:55
  • My attempt: p[0].merge(p[1]){|k,v1,v2| (v1+v2)/2 unless v1 && v2 == "INR"} Commented Jun 13, 2017 at 15:56
  • It's a bit hard to read code in comments, you you edit your question instead? Commented Jun 13, 2017 at 15:56

4 Answers 4

3

Irb

2.2.3 :011 > b = {test1: 30, test2: 40}
 => {:test1=>30, :test2=>40} 
2.2.3 :012 > a = {test1: 20, test2: 60}
 => {:test1=>20, :test2=>60} 
2.2.3 :013 > c = a.merge(b){|key, oldval, newval| (newval + oldval)/2}
 => {:test1=>25, :test2=>50} 
Sign up to request clarification or add additional context in comments.

1 Comment

ohh I thought it was some thing else.
2

The accepted answer will not work for more than 2 hashes, since merge works only 2 by 2 and you are calculating average here.

(((3 + 2) / 2) + 2.5) / 2 is different from (3 + 2 + 2.5) / 3

So I wrote a piece of code that could do what you want for whatever size of array you have

  def self.merge_all_and_average(array)
    new_hash = {}
    unless array.empty?
      array[0].keys.each do |key|
        if array[0][key].class == Fixnum
          total = array.map { |i| i[key] }.inject(0) { |sum, x| sum + x }
          new_hash = new_hash.merge(key => total / array.size)
        else
          new_hash = new_hash.merge(key => array[0][key])
        end
      end
    end
    new_hash
  end

1 Comment

I know it won't work for more than two hash. I just wanted an idea on how I could handle it. I was on the right path but couldn't get correct answer. My problem never was totally different and various other things were involved. This was just a part of it where I was getting confused. The solution I have used is somewhat similar to yours. Check my edit.
2

This should work with any number of hashes:

data = [
  { amount: 897_500, gross_amount: 897_500, tax: 147_500, hotel_fees: 0, base_fare: 750_000, currency: 'INR' },
  { amount: 1_006_500, gross_amount: 1_006_500, tax: 156_500, hotel_fees: 0, base_fare: 850_000, currency: 'INR' },
  { amount: 1_006_500, gross_amount: 1_006_500, tax: 156_500, hotel_fees: 0, base_fare: 850_000, currency: 'INR' }
]

transposed_hashes = data.each_with_object(Hash.new{|h, k| h[k] = []}) do |h, mem|
  h.each do |k, v|
    mem[k] << v
  end
end
# {:amount=>[897500, 1006500, 1006500], :gross_amount=>[897500, 1006500, 1006500], :tax=>[147500, 156500, 156500], :hotel_fees=>[0, 0, 0], :base_fare=>[750000, 850000, 850000], :currency=>["INR", "INR", "INR"]}

average_hash = transposed_hashes.map do |k, v|
  new_value = if v[0].is_a? Integer
                v.sum.to_f / v.size
              else
                v[0]
              end
  [k, new_value]
end.to_h

puts average_hash
# {:amount=>970166.6666666666, :gross_amount=>970166.6666666666, :tax=>153500.0, :hotel_fees=>0.0, :base_fare=>816666.6666666666, :currency=>"INR"}

1 Comment

I am doing something similar as well but this method increases my complexity of the code.
1

For two hashes in array you could use inject and merge checking if the value for the both currency keys are Fixnum class, if not, then take the value of currency "INR" in the first hash and use it:

array = [
  {:amount=>897500,  :gross_amount=>897500,  :tax=>147500, :hotel_fees=>0, :base_fare=>750000, :currency=>"INR"}, 
  {:amount=>1006500, :gross_amount=>1006500, :tax=>156500, :hotel_fees=>0, :base_fare=>850000, :currency=>"INR"}
]

p array.inject{|k,v| k.merge(v){|_,a,b| [a,b].all?{|e| e.is_a?(Fixnum)} ? (a+b)/2 : b}}
# => {:amount=>952000, :gross_amount=>952000, :tax=>152000, :hotel_fees=>0, :base_fare=>800000, :currency=>"INR"}

For two or more hashes in an array you could try with:

main_array = [
  {:amount=>897500,  :gross_amount=>897500,  :tax=>147500, :hotel_fees=>0, :base_fare=>750000, :currency=>"INR"}, 
  {:amount=>1006500, :gross_amount=>1006500, :tax=>156500, :hotel_fees=>0, :base_fare=>850000, :currency=>"INR"},
  {:amount=>1006500, :gross_amount=>1006500, :tax=>156500, :hotel_fees=>0, :base_fare=>850000, :currency=>"INR"},
]
array_result = main_array.flat_map(&:to_a).group_by(&:first).map do |key, array| 
  { 
    key => (
      result = array.inject(0) do |total, (_, value)| 
        value.is_a?(Fixnum) ? total + value : value
      end 
      result.is_a?(Fixnum) ? result / main_array.size : result 
    ) 
  } 
end
p array_result

4 Comments

What is _ in |_,a,b| doing? Can you please explain?
@Ravi it's explained in my answer, first attribute is key, he called it _ second attribute is the old value, or value from the first hash, third attribute is the new value or value from the second hash
_ corresponds to the hash's key, it could be named as you want, but as I'm not using it but it must be references then I assign it as _, that means that's something that's needed to make the block works, but won't be used inside.
The question mentions "multiple hashes", not just 2 hashes.

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.