I am trying to implement a cumulative weighted average function that takes as argument the list
[[1000, 3.1], [500, 1.2], [800, 7.1], [1300, 8.88]]
and returns (rounded to 2 decimal places here)
[3.1, 2.47, 4.08, 5.81]
For, example: 2.47 = (1000 * 3.1 + 500 * 1.2) / 1500.
I have currently solved this using the following piece of code:
def cumulative_weighted_average(list)
cs = 0
qu = 0
res = list.inject([0]) do |s, el|
cs += el[0] * el[1]
qu += el[0]
s + [cs.to_f / qu]
end
res.shift
res
end
Is there a shorter (more compact) way of doing this?
Edit: Thanks for the answers below! The list will on average contain about 1000 entries, so not sure about the speed requirement. Since I need to be able to essentially track two values within the block, is there some extension of inject that allows you to write
list.inject([0,0]){ |s1, s2, el| ...}
where s1 and s2 are initialized to 0?
I think this is what you want:
def cumulative_weighted_average list
cs, qu = 0.0, 0.0
list
.map{|x, w| [cs += x * w, qu += x]}
.map{|cs, qu| cs / qu}
end
cumulative_weighted_average([[1000, 3.1], [500, 1.2], [800, 7.1], [1300, 8.88]])
# => [3.1, 2.466666666666667, 4.078260869565217, 5.812222222222222]
list.inject([0,0]){|(s1, s2), el| ...}
Is there a shorted (more compact) way of doing this?
I can try for you..
arr = [[1000, 3.1], [500, 1.2], [800, 7.1], [1300, 8.88]]
arr2 = (1..arr.size).map do |i|
b = arr.take(i)
b.reduce(0){|sum,a| sum + a.reduce(:*)}/b.reduce(0){|sum,k| sum + k[0]}
end
arr2
# => [3.1, 2.466666666666667, 4.078260869565217, 5.812222222222222]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With