I am trying to do the following in as little code as possible and as functionally as possible:
def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double
Obviously the following works:
= (floor -> cap) match {
case (None, None) => amt
case (Some(f), None) => f max amt
case (None, Some(c)) => c min amt
case (Some(f), Some(c)) => (f max amt) min c
}
I was really hoping for something more elegant and will accept use of the Scalaz library! You can assume that the following is true:
floor.forall( f => cap.forall( _ > f))
If anyone is interested, here is some test code:
object Comparisons {
sealed trait Cf {
def restrict(floor: Option[Double], cap: Option[Double], amt: Double): Double
}
def main(args: Array[String]) {
val cf : Cf = //TODO - your impl here!
def runtest(floor: Option[Double], cap: Option[Double], amt: Double, exp : Double) : Unit = {
val ans = cf.restrict(floor, cap, amt)
println("floor=%s, cap=%s, amt=%s === %s (%s) : %s".format(floor, cap, amt, ans, exp, if (ans == exp) "PASSED" else "FAILED"))
}
runtest(Some(3), Some(5), 2, 3)
runtest(Some(3), Some(5), 3, 3)
runtest(Some(3), Some(5), 4, 4)
runtest(Some(3), Some(5), 5, 5)
runtest(Some(3), Some(5), 6, 5)
runtest(Some(3), None, 2, 3)
runtest(Some(3), None, 3, 3)
runtest(Some(3), None, 4, 4)
runtest(Some(3), None, 5, 5)
runtest(Some(3), None, 6, 6)
runtest(None, Some(5), 2, 2)
runtest(None, Some(5), 3, 3)
runtest(None, Some(5), 4, 4)
runtest(None, Some(5), 5, 5)
runtest(None, Some(5), 6, 5)
runtest(None, None, 2, 2)
runtest(None, None, 3, 3)
runtest(None, None, 4, 4)
runtest(None, None, 5, 5)
runtest(None, None, 6, 6)
}
}
Edit 2:
While thinking about the cataX
method, I figured out that cataX
is nothing else than a plain and simple fold. Using that, we can get a pure scala solution without any additional libraries.
So, here it is:
( (amt /: floor)(_ max _) /: cap)(_ min _)
which is the same as
cap.foldLeft( floor.foldLeft(amt)(_ max _) )(_ min _)
(not that this is necessarily easier to understand).
I think you can’t have it any shorter than that.
For better or worse, we can also solve it using scalaz:
floor.map(amt max).getOrElse(amt) |> (m => cap.map(m min).getOrElse(m))
or even:
floor.cata(amt max, amt) |> (m => cap.cata(m min, m))
As a ‘normal’ Scala programmer, one might not know about the special Scalaz operators and methods used (|>
and Option.cata
). They work as follows:
value |> function
translates to function(value)
and thus amt |> (m => v fun m)
is equal to v fun amt
.
opt.cata(fun, v)
translates to
opt match {
case Some(value) => fun(value)
case None => v
}
or opt.map(fun).getOrElse(v)
.
See the Scalaz definitions for cata
and |>
.
A more symmetric solution would be:
amt |> (m => floor.cata(m max, m)) |> (m => cap.cata(m min, m))
Edit: Sorry, it’s getting weird now, but I wanted to have a point-free version as well. The new cataX
is curried. The first parameter takes a binary function; the second is a value.
class CataOption[T](o: Option[T]) {
def cataX(fun: ((T, T) => T))(v: T) = o.cata(m => fun(m, v), v)
}
implicit def option2CataOption[T](o: Option[T]) = new CataOption[T](o)
If o
matches Some
we return the function with the value of o
and the second parameter applied, if o
matches None
we only return the second parameter.
And here we go:
amt |> floor.cataX(_ max _) |> cap.cataX(_ min _)
Maybe they already have this in Scalaz…?
Not quite as terse as the scalaz version, but on the other hand, no dependencies,
List(floor.getOrElse(Double.NegativeInfinity), cap.getOrElse(Double.PositiveInfinity), amt).sorted.apply(1)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With