39

I've got a List of days in the month:

val days = List(31, 28, 31, ...)

I need to return a List with the cumulative sum of days:

val cumDays = List(31, 59, 90)

I've thought of using the fold operator:

(0 /: days)(_ + _)

but this will only return the final result (365), whereas I need the list of intermediate results.

Anyway I can do that elegantly?

lindelof
  • 34,556
  • 31
  • 99
  • 140

8 Answers8

61

Scala 2.8 has the methods scanLeft and scanRight which do exactly that.

For 2.7 you can define your own scanLeft like this:

def scanLeft[a,b](xs:Iterable[a])(s:b)(f : (b,a) => b) =
  xs.foldLeft(List(s))( (acc,x) => f(acc(0), x) :: acc).reverse

And then use it like this:

scala> scanLeft(List(1,2,3))(0)(_+_)
res1: List[Int] = List(0, 1, 3, 6)
sepp2k
  • 363,768
  • 54
  • 674
  • 675
22

I'm not sure why everybody seems to insist on using some kind of folding, while you basically want to map the values to the cumulated values...

val daysInMonths = List(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)

val cumulated = daysInMonths.map{var s = 0; d => {s += d; s}}

//--> List[Int] = List(31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365)
Landei
  • 54,104
  • 13
  • 100
  • 195
  • 4
    Because if there's one solution using a fold and another solution using mutable variables, most people (with an FP background) would chose the fold. – sepp2k Jul 12 '10 at 09:59
  • 10
    I don't see any problems with this: The var is not exposed, the solution is easy to understand, short and readable, and the calculation is efficient. Sure, usage of mutability should be limited and not overdone, but immutability is just a tool and not a dogma - at least in any language which offers mutability. – Landei Jul 12 '10 at 11:30
  • 4
    The definition of `foldLeft` uses `var`s, so using them here is fine IMO. Sometimes mutable data is more efficient and clearer, and this is a good example. – Luigi Plinge Jul 24 '11 at 00:10
  • 3
    You know what's especially nice about seeing this solution? Learning that multiple expressions can be passed as an argument to `map`, and that the scoping rules are such that the mutable variable is contained. I haven't seen that elsewhere, but it opens a lot of doors... – Jim Pivarski Jun 08 '15 at 15:55
  • @Jim Pivarski : To be precise, what the map method receives as argument is only the last statement of the block, `d => {s += d; s}`. However, this lambda contains a variable `s`from its generating context, which it "captures", as Scala has not only something like function pointers, but real closures. See e.g. http://stackoverflow.com/questions/208835/function-pointers-closures-and-lambda – Landei Sep 25 '15 at 14:32
  • Where's your state monad? – dividebyzero Sep 04 '16 at 18:28
  • This works for List but generally you should be aware that using this pattern for lazy collections will broke things! See my answer below! – Scalway Dec 22 '19 at 13:20
6

You can simply perform it:

daysInMonths.foldLeft((0, List[Int]()))
                     {(acu,i)=>(i+acu._1, i+acu._1 :: acu._2)}._2.reverse
mucar
  • 69
  • 2
  • 3
2

Fold into a list instead of an integer. Use pair (partial list with the accumulated values, accumulator with the last sum) as state in the fold.

Mau
  • 14,234
  • 2
  • 31
  • 52
1

Fold your list into a new list. On each iteration, append a value which is the sum of the head + the next input. Then reverse the entire thing.

scala> val daysInMonths = List(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
daysInMonths: List[Int] = List(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)

scala> daysInMonths.foldLeft(Nil: List[Int]) { (acc,next) => 
     | acc.firstOption.map(_+next).getOrElse(next) :: acc    
     | }.reverse                                             
res1: List[Int] = List(31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365)
Synesso
  • 37,610
  • 35
  • 136
  • 207
1

You can also create a monoid class that concatenates two lists while adding to the second one the last value from the first. No mutables and no folds involved:

case class CumSum(v: List[Int]) { def +(o: CumSum) = CumSum(v ::: (o.v map (_ + v.last))) }
defined class CumSum

scala> List(1,2,3,4,5,6) map {v => CumSum(List(v))} reduce (_ + _)
res27: CumSum = CumSum(List(1, 3, 6, 10, 15, 21))
dividebyzero
  • 2,190
  • 1
  • 21
  • 33
1

For any:

val s:Seq[Int] = ...

You can use one of those:

s.tail.scanLeft(s.head)(_ + _)
s.scanLeft(0)(_ + _).tail

or folds proposed in other answers but... be aware that Landei's solution is tricky and you should avoid it.

BE AWARE

s.map { var s = 0; d => {s += d; s}} 
//works as long `s` is strict collection

val s2:Seq[Int] = s.view //still seen as Seq[Int]
s2.map { var s = 0; d => {s += d; s}} 
//makes really weird things! 
//Each value'll be different whenever you'll access it!

I should warn about this as a comment below Landei's answer but I couldn't :(.

Scalway
  • 1,633
  • 10
  • 18
0

Works on 2.7.7:

def stepSum (sums: List [Int], steps: List [Int]) : List [Int] = steps match { 
     case Nil => sums.reverse.tail                                                  
     case x :: xs => stepSum (sums.head + x :: sums, steps.tail) }

days
res10: List[Int] = List(31, 28, 31, 30, 31)

stepSum (List (0), days) 
res11: List[Int] = List(31, 59, 90, 120, 151)
user unknown
  • 35,537
  • 11
  • 75
  • 121