4

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 Answers 5

8

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for the answer. What if I want to throw an exception instead of default?
You just throw an exception instead of default :) case (None, None) => throw new Exception("your exception goes here").
4

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) 

2 Comments

Don't need the .toSeq.
@jwvh yeah, I thought about that, but wanted to make it explicit...
1
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.

Comments

0

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.

Comments

0

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 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.