Handling two Options in Scala

2.4k views Asked by At

I have 2 options, and I need to take average of the values they hold.

It is possible that one or both may be missing. If one of the value is missing, I would just take other one as the average. But if both are missing, I would resort to some default value.

How can this be done in a clean way?

I can check absence of value using isEmpty, but then won't that would be same as null check?

5

There are 5 answers

2
J0HN On BEST ANSWER

I guess this is self-explanatory:

val option1 = Some(12.0)
val option2 = None
val default = 0.0

val average = (option1, option2) match {
   case (Some(val1), Some(val2)) => (val1 + val2) / 2
   case (None, Some(val2)) => val2
   case (Some(val1), None) => val1
   case (None, None) => default
}

... but if not, the basic idea is that you construct a tuple of options, and then pattern match on the tuple.

This has a benefit of explicitly capturing all the four potential cases + having support from the complier - since Option is a sealed trait, compiler can check and ensure that all the potential branches of pattern match are covered.

2
cbley On

You could treat the Options as Seq:

val o: Option[Double]
val p: Option[Double]
val default: Double

val s = o.toSeq ++ p.toSeq

val avg = s.reduceOption(_ + _).getOrElse(default) / 1.max(s.size)
0
Tim On
val v = List(opt1, opt2).flatten

if (v.nonEmpty) {
  v.sum / v.size
} else {
  <default value>
}

This can be extended to work with any number of optional values.

0
Scalway On

In my opinion you should keep average in option and grab default afterwards.

def avgOpt(of:Option[Double]*) = {
  val s = of.flatten
  s.reduceOption(_ + _).map(_ / s.size)
}

avgOpt(Some(5), None, None).getOrElse(0)    //5
avgOpt(Some(5), Some(3), None).getOrElse(0) //4
avgOpt(None, None).getOrElse(0)             //0
0
stefanobaghino On

Another possibility:

def avg(left: Option[Double], right: Option[Double])(default: => Double): Double =
  left.flatMap(a => right.map(b => (a + b) / 2))
    .orElse(left)
    .orElse(right)
    .getOrElse(default)

You flatMap over the left option: if it's not empty, you take the right option and map its content and average with the content of the left option. If either option is empty, the result is None, so you can defined either left or right as fallback values with orElse. Finally, the result is retrieved with getOrElse and if both inputs where empty, the default is returned.

You can adapt this to adopt any behavior. To make a function that throws if both options are empty you can do the following:

val assertAvg = avg(_ : Option[Double], _ : Option[Double])(sys.error("both operands are empty"))

This works because the type of throw expressions is Nothing, which is a subtype of any other type (including Double), i.e. it can be returned as a result of any expression, regardless the expected type.

The code (and some tests) are available here on Scastie.