How do I find the min() or max() of two Option[Int]
Solution 1
I think this is what you're after:
val minValue = List(i1, i2).flatten match {
case Nil => defaultValue
case xs => xs.min
}
I'd avoid sorted
since sorting requires a lot more processing than simply finding the max or min (although it probably doesn't make much difference in this case).
Solution 2
Update: I just noticed that my solution below and the one in your answer behave differently—I read your question as asking for the minimum of the two values when there are two values, but in your answer you're effectively treating None
as if it contained a value that's either bigger (for min
) or smaller (for max
) than anything else.
To be more concrete: if i1
is Some(1)
and i2
is None
, my solution will return the default value, while yours will return 1.
If you want the latter behavior, you can use the default semigroup instance for Option[A]
and the tropical semigroup for Int
. In Scalaz 7, for example, you'd write:
import scalaz._, Scalaz._
optionMonoid(Semigroup.minSemigroup[Int]).append(i1, i2) getOrElse defaultValue
Or the following shorthand:
Tags.Min(i1) |+| Tags.Min(i2) getOrElse defaultValue
It's not as clean as the applicative functor solution below, but if that's your problem, that's your problem.
Here's a more idiomatic way that doesn't involve creating an extra list:
(for { x <- i1; y <- i2 } yield math.min(x, y)) getOrElse defaultValue
Or, equivalently:
i1.flatMap(x => i2.map(math.min(x, _))) getOrElse defaultValue
What you're doing is "lifting" a two-place function (min
) into an applicative functor (Option
). Scalaz makes this easy with its applicative builder syntax:
import scalaz._, Scalaz._
(i1 |@| i2)(math.min) getOrElse defaultValue
The standard library solution isn't much less elegant in this case, but this is a useful abstraction to know about.
Solution 3
I solved a similar problem using the following approach. We handle a special case when both of the options have values, otherwise we use an API method Option.orElse
.
val a: Option[Int] = Some(10)
val b: Option[Int] = Some(20)
val c: Option[Int] = (a, b) match {
case (Some(x), Some(y)) => Some(x min y)
case (x, y) => x orElse y
}
Solution 4
val minValue: Int = List(i1, i2).flatten.sorted.headOption getOrElse defaultValue
Solution 5
You can use patterns in for expressions, values that do not match the pattern are discarded.
(for (Some(x) <- List(None, Some(3))) yield x) max
Not as good as the List.flatten approach though.
Graham Lea
Want to understand your software architecture better? You should check out Archium! Or you can just read more about me.
Updated on June 05, 2022Comments
-
Graham Lea about 2 years
How would you find
minValue
below? I have my own solution but want to see how others would do it.val i1: Option[Int] = ... val i2: Option[Int] = ... val defaultValue: Int = ... val minValue = ?
-
Graham Lea over 11 yearsThanks for the contribution, but I wanted the min value if either one was defined, not only when both are defined.
-
Graham Lea over 11 yearsThanks for your answer, Travis. It's pleasing in its brevity, though there's two things that turn me off using it: it uses a third party library, and in order to understand it I need to read an article about "a relatively new area in mathematics and algebraic geometry".
-
Graham Lea over 11 yearsI think this is probably the best way, though it's unfortunate that the idiomatic style makes it four lines long. I tried to shorten it (by removing match) and came up with
((xs:List[Int]) => {if (xs == Nil) defaultValue else xs.min})(List(i1, i2).flatten)
but this is clearly obfuscation for the sake of brevity. -
user40171 almost 8 yearswhy not
List(Option(defaultValue), i1, i2).flatten.min
? -
Luis Ramirez-Monterosa over 7 yearsflatten.min would throw an UnsupportedOperationException with empty.min if List is empty, when you flatten you can end up with an empty list if all elements inside the list are None
-
Boris Azanov about 3 yearsI added a little more actual way using cats for that.