37

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]
1
  • 1
    coming back to this question myself, a mere 5.5 years later! Commented Jul 18, 2015 at 5:10

8 Answers 8

50
class Array
  def cumulative_sum
    sum = 0
    self.map{|x| sum += x}
  end
end
Sign up to request clarification or add additional context in comments.

Comments

14

Here is one way

a = [1, 2, 3, 4]
a.inject([]) { |x, y| x + [(x.last || 0) + y] }

If it is OK that the answer is more than one statement, then this would be cleaner:

outp = a.inject([0]) { |x, y| x + [x.last + y] }
outp.shift # To remove the first 0

Comments

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

1 Comment

+1 for the reference to scanr, that's what an accumulated inject is. Note that you can add scanr to module Enumerable instead of just class Array.
2

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]

2 Comments

The link is broken (404).
Yep ( there are a lot of links to it but it's dead. I hope someone find the mirror.
1

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.

1 Comment

Can be modified to cs.last.to_i as well - not shorter, but maybe more readable ?
1

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]

Comments

1

Try this code

[1,2,3,4].inject([]){ |acc, value| acc << acc.last.to_i + value.to_i }

=> [1, 3, 6, 10]

Comments

0

The Haskell function we want is scanl, not scanr.

class Array
  def scanl(init)
    self.reduce([init]) { |a, e| a.push(yield(a.last, e)) }
  end
end

[1,2,3,4].scanl(0) { |a, b| a + b }[1..-1]
=> [1, 3, 6, 10]

Comments

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.