What is the slickest, most Ruby-like way of calculating the cumulative sum of an array?
Example:
[1,2,3,4].cumulative_sum
should return
[1,3,6,10]
What is the slickest, most Ruby-like way of calculating the cumulative sum of an array?
Example:
[1,2,3,4].cumulative_sum
should return
[1,3,6,10]
irb> a = (1..10).to_a
#=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
irb> a.inject([0]) { |(p,*ps),v| [v+p,p,*ps] }.reverse[1..-1]
#=> [1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
We could also take a note from Haskell and make a ruby version of scanr.
irb> class Array
> def scanr(init)
> self.inject([init]) { |ps,v| ps.unshift(yield(ps.first,v)) }.reverse
> end
> end
#=> nil
irb> a.scanr(0) { |p,v| p + v }
=> [0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
irb> a.scanr(0) { |p,v| p + v }[1..-1]
=> [1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
irb> a.scanr(1) { |p,v| p * v }
=> [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]
Also you can read about scanl — feature you need, but that isn't yet implement in Ruby, as far as I know. Here are examples and sample source code of it: http://billsix.blogspot.com/2008/11/functional-collection-patterns-in-ruby.html
UPD: The above link is dead, so instead I'll say that my Ruby implementation of Wolfram Mathematica's FoldList[] as a part of gem "mll" here can be simplified for OP's purpose while being Enumerable:
def fold_list array
start = 0
Enumerator.new do |e|
array.each do |i|
e << start += i
end
end
end
irb> fold_list([1,2,3]).to_a
=> [1, 3, 6]
One more approach ( though I prefer khell's)
(1..10).inject([]) { |cs, i| cs << i + (cs.last || 0) }
I saw the answer posted by hrnt after posting my answer. Though two approaches look the same, solution above is more efficient as the same array is used in each inject cycle.
a,r = [1, 2, 3, 4],[]
k = a.inject(r) { |x, y| x + [(x.last || 0) + y] }
p r.object_id
# 35742260
p k.object_id
# 35730450
You will notice r and k are different. If you do the same test for the solution above:
a,r = [1, 2, 3, 4],[]
k = a.inject(r) { |cs, i| cs << i + (cs.last || 0) }
p r.object_id
# 35717730
p k.object_id
# 35717730
The object id for r and k are the same.
unearthing this for one more approach, that modifies the array in-place
class Array
def cumulative_sum!
(1..size-1).each {|i| self[i] += self[i-1] }
self
end
end
can also be generalized :
def cumulate!( &block )
(1..size-1).each {|i| self[i] = yield self[i-1], self[i] }
self
end
>> (1..10).to_a.cumulate! {|previous, next| previous * next }
=> [1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]