简体   繁体   中英

In Scala, how do I fold a List and return the intermediate results?

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?

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)

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)

You can simply perform it:

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

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.

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)

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

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)

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 :(.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM