Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sum a list of options in Scala

Tags:

list

scala

sum

How can I sum a list of options List[Option[Double]] with the following rules?

  • List(Some(1), ..., Some(n)) --> Some(1 + ... + n)
  • List(Some(1), ..., Some(n), None) --> None
  • List(None, ..., None) --> None
like image 928
DennisVDB Avatar asked Sep 05 '16 20:09

DennisVDB


3 Answers

This is a straight foldMap method call, if you're using Cats, you should probably just use that, as it folds over a collection summing the values using a Monoid.

Otherwise, to avoid the traversal of the entire list with a forall to check if the entire set of options is defined, you could use a foldLeft, and use the fact that you can yield a None the first time you find an empty element in the chain.

def sumList[ T ] (list: List[Option[T]])(implicit ev: Numeric[T]): Option[T] = {
  list.foldLeft(Option(ev.zero)) { case (acc, el) => 
    el.flatMap(value => acc.map(ac => ev.plus(ac, value)))
  }
}

sumList(List(None, None, Some(5)))
res10: Option[Int] = None

scala> sumList(List(None, None, Some(5F)))
res11: Option[Float] = None

scala> sumList[Double](List(None, None, None))
res13: Option[Double] = None

scala> sumList(List(Some(5), Some(15)))
res14: Option[Int] = Some(20)

And to avoid return you could simply use recursion(update, return is not needed above, but maybe this is easier to follow):

@annotation.tailrec
def sumListRec[T](list: List[Option[T]], acc: T)(implicit ev: Numeric[T]): Option[T] = {
  list match {
    // if the list still has elements
    case head :: tail => head match {
      // add the value to the accumulator and keep going
      case Some(value) => sumListRec(tail, ev.plus(acc, value))
      // if you found a None, disregard whatever sum we built so far
      // and just return None
      case None => None
    }
    // If the list is empty, it means we've successfully reached
    // the end of the list, so we just return the sum we built.
    case Nil => Some(acc)
  }
}

Watch it in action:

scala> sumListRec(List(Some(5D), Some(5D)), 0D)
res5: Option[Double] = Some(10.0)

sumListRec(List(None, None, Some(5D)), 0D)
res2: Option[Double] = None

scala> sumListRec(List(None, None), 0D)
res6: Option[Double] = None
like image 191
flavian Avatar answered Oct 13 '22 00:10

flavian


If you use Scalaz this is super easy:

targetList.sequence.map(_.suml)
like image 41
Sean Parsons Avatar answered Oct 12 '22 23:10

Sean Parsons


There is even a simpler way just using fold:

val ls = List(Some(2.1d), Some(5.6d), Some(4.3d), Some(1.2))

ls.fold(Option(0d))((rs,x) => for(n <- x; m <- rs) yield {n+m})

=> Some(13.2)

val ls = List(Some(2.1d),None, Some(5.6d), Some(4.3d), Some(1.2))

ls.fold(Option(0d))((rs,x) => for(n <- x; m <- rs) yield {n+m})

=> None

like image 20
A Monad is a Monoid Avatar answered Oct 12 '22 23:10

A Monad is a Monoid