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 = ?
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 = ?
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).
List(Option(defaultValue), i1, i2).flatten.min
? –
Lau 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.
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
}
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).
((xs:List[Int]) => {if (xs == Nil) defaultValue else xs.min})(List(i1, i2).flatten)
but this is clearly obfuscation for the sake of brevity. –
Lubra List(Option(defaultValue), i1, i2).flatten.min
? –
Lau We can combine the 2 Option
s as an Iterable
with Option
's ++
operator, which allows us to use minOption
(to nicely handle the case of the empty iterable formed by the None/None
case) and fallback on a default value if necessary with getOrElse
:
(optionA ++ optionB).minOption.getOrElse(-1)
// None and None => -1
// Some(5) and None => 5
// None and Some(5) => 5
// Some(5) and Some(3) => 3
val minValue: Int = List(i1, i2).flatten.sorted.headOption getOrElse defaultValue
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.
Another option which wasn't mentioned is using reduceLeftOption
(interchange math.max
and math.min
as desired):
val min = (first ++ second).reduceLeftOption(math.min).getOrElse(defaultValue)
scala> val first = Some(10)
first: Some[Int] = Some(10)
scala> val second: Option[Int] = None
second: Option[Int] = None
scala> val defaultMin = -1
defaultMin: Int = -1
scala> (first ++ second).reduceLeftOption(math.min).getOrElse(defaultMin)
res7: Int = 10
scala> val first: Option[Int] = None
first: Option[Int] = None
scala> (first ++ second).reduceLeftOption(math.min).getOrElse(defaultMin)
res8: Int = -1
scala> val first = Some(10)
first: Some[Int] = Some(10)
scala> val second = Some(42)
second: Some[Int] = Some(42)
scala> (first ++ second).reduceLeftOption(math.min).getOrElse(defaultMin)
res9: Int = 10
If you want to avoid using scalaz and map/for/getOrElse, you can do the following:
val minValue = (i1, i2) match {
case (Some(x), Some(y)) => math.min(x, y)
case _ => defaultValue
}
You can do that you need elegant using custom cats Semigroup
instances:
import cats.kernel.Semigroup
import cats.instances.option._ // this import is for cats std option combiner
import cats.syntax.semigroup._
object Implicits {
implicit val intMinSemigroup: Semigroup[Int] =
(x: Int, y: Int) => math.min(x, y)
implicit val intMaxSemigroup: Semigroup[Int] =
(x: Int, y: Int) => math.max(x, y)
}
import Implicits.intMinSemigroup
// these are results for minSemigroup
// List((Some(1),Some(1),Some(2)), (Some(1),Some(1),None), (None,Some(2),Some(2)), (None,None,None))
//import Implicits.intMaxSemigroup
// these are results for maxSemigroup
// List((Some(1),Some(2),Some(2)), (Some(1),Some(1),None), (None,Some(2),Some(2)), (None,None,None))
for {
maybeA <- Seq(Some(1), None)
maybeB <- Seq(Some(2), None)
} yield (maybeA, maybeA |+| maybeB, maybeB)
if you want replace None
by default value you can use combine twice:
val defaultValue: Int = 3
val optionMin = for {
maybeA <- Seq(Some(1), None)
maybeB <- Seq(Some(2), None)
} yield (maybeA |+| maybeB) |+| Some(defaultValue)
// List(Some(1), Some(1), Some(2), Some(3))
Shortly, Semigroup[A]
is typeclass for combining two values of the same type A
into the one value of type A
.
Here we use std cats OptionMonoid
(it extends Semigroup[Option[A]]
) here source code:
class OptionMonoid[A](implicit A: Semigroup[A]) extends Monoid[Option[A]] {
def empty: Option[A] = None
def combine(x: Option[A], y: Option[A]): Option[A] =
x match {
case None => y
case Some(a) =>
y match {
case None => x
case Some(b) => Some(A.combine(a, b))
}
}
}
We see that it takes option matching on his own and everything what we should give him to work is implicit A: Semigroup[A]
. In our case we write two different combiners for min
, max
cases:
object Implicits {
implicit val intMinSemigroup: Semigroup[Int] =
(x: Int, y: Int) => math.min(x, y)
implicit val intMaxSemigroup: Semigroup[Int] =
(x: Int, y: Int) => math.max(x, y)
}
So, we import combiners (i.e. import Implicits.intMinSemigroup
) and just use cats.syntax.semigroup
for using combine function as operator |+|
:
maybeA |+| maybeB
.
In conclusion, you can just define your custom semigroup for any type (not only Int
) and combine options of this type after importing some cats syntax and instances.
Since Seq[Option[Element]].min
evaluates to None
if Seq[Option[Element]].exists(_.isEmpty)
(None) is true
, while Seq[Option[Element]].max
evaluates to Some(element)
if Seq[Option[Element]].exists(_.nonEmpty)
is true, I resort to double reversion, with the result of finding min
by using maxBy
:
println((for {
a <- Seq(None, Some(3))
b <- Seq(None, Some(4))
} yield Seq(a, b, Seq(a, b).maxBy(_.map(-_)).getOrElse(-666))
.mkString(" -> ")
).mkString("\n")
)
with the result output of:
None -> None -> -666
None -> Some(4) -> 4
Some(3) -> None -> 3
Some(3) -> Some(4) -> 3
Another solution would be that of tweaking/implementing Ordering[Option[T]]
© 2022 - 2024 — McMap. All rights reserved.
((xs:List[Int]) => {if (xs == Nil) defaultValue else xs.min})(List(i1, i2).flatten)
but this is clearly obfuscation for the sake of brevity. – Lubra