10

我正在尝试以尽可能少的代码和尽可能多的功能执行以下操作:

def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double

显然,以下工作:

= (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
  }

我真的希望有更优雅的东西,并且会接受使用Scalaz库!您可以假设以下情况为真

floor.forall( f => cap.forall( _ > f))

如果有人有兴趣,这里是一些测试代码

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)
  }
}
4

13 回答 13

16

编辑 2

在考虑cataX方法时,我发现这cataX只不过是一个简单的折叠。使用它,我们可以获得一个纯粹的 scala 解决方案,而无需任何额外的库。

所以,这里是:

( (amt /: floor)(_ max _) /: cap)(_ min _)

这与

cap.foldLeft( floor.foldLeft(amt)(_ max _) )(_ min _)

(并不是说这一定更容易理解)。

我认为你不能比这更短。


无论好坏,我们也可以使用 scalaz 来解决它:

floor.map(amt max).getOrElse(amt) |> (m => cap.map(m min).getOrElse(m))

甚至:

floor.cata(amt max, amt) |> (m => cap.cata(m min, m))

作为一名“普通”Scala 程序员,可能不知道使用的特殊 Scalaz 运算符和方法 (|>Option.cata)。它们的工作方式如下:

value |> function转换为function(value)并因此amt |> (m => v fun m)等于v fun amt

opt.cata(fun, v)翻译成

opt match {
  case Some(value) => fun(value) 
  case None => v
}

opt.map(fun).getOrElse(v)

请参阅 Scalaz 定义cata|>

一个更对称的解决方案是:

amt |> (m => floor.cata(m max, m)) |> (m => cap.cata(m min, m))

编辑:对不起,现在变得很奇怪,但我也想要一个无点版本。新cataX的咖喱。第一个参数采用二元函数;第二个是一个值。

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)

如果o匹配Some,我们返回具有值o和应用第二个参数的函数,如果o匹配,None我们只返回第二个参数。

现在我们开始:

amt |> floor.cataX(_ max _) |> cap.cataX(_ min _)

也许他们在 Scalaz 中已经有了这个……?

于 2010-11-10T15:04:56.363 回答
15

不像 scalaz 版本那么简洁,但另一方面,没有依赖关系,

List(floor.getOrElse(Double.NegativeInfinity), cap.getOrElse(Double.PositiveInfinity), amt).sorted.apply(1)
于 2010-11-10T15:38:29.450 回答
5

我将从这个开始:

def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double = {
  val flooring = floor.map(f => (_: Double) max f).getOrElse(identity[Double] _)       
  val capping  = cap.map(f => (_: Double) min f).getOrElse(identity[Double] _)         
  (flooring andThen capping)(amt)                                                      
}                                                                                    

但我觉得我在这里错过了一些机会,所以我可能还没有完成。

于 2010-11-10T15:37:15.110 回答
5
于 2010-11-11T21:51:33.340 回答
4

这个怎么样?

//WRONG
def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double = 
   (floor.getOrElse(amt) max amt) min cap.getOrElse(amt)

[编辑]

第二次尝试:

def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double = 
   floor.map(f => f max _).getOrElse(identity[Double] _)(
     cap.map(c => c min _).getOrElse(identity[Double] _)(amt))

对于我的口味来说,看起来有点“口齿不清”,但通过了测试:-)

[第二次编辑]

第一个版本也可以“修复”:

def restrict(floor: Option[Double], cap: Option[Double], amt: Double): Double =
  (floor.getOrElse(-Double.MaxValue) max amt) min cap.getOrElse(Double.MaxValue)
于 2010-11-10T14:44:20.913 回答
4

不是更漂亮,也不是更短,当然也不是更快!但它更可组合、更通用、更“实用”:

编辑:使代码完全通用:)

def optWith[T](a: Option[T], b: T)(op:(T,T)=>T) =
  a map (op(b,_)) getOrElse b

def optMin[T:Numeric](a: Option[T]) =
  (b:T) => optWith(a, b)(implicitly[Numeric[T]].min)

def optMax[T:Numeric](a: Option[T]) =
  (b:T) => optWith(a, b)(implicitly[Numeric[T]].max)

def restrict[T,FT,CT](x:T, floor:Option[FT], ceil:Option[CT])
  (implicit ev:Numeric[T], fv:FT=>T, cv:CT=>T) =
  optMin(ceil map cv) compose optMax(floor map fv) apply(x)

更新2:还有这个版本,更好地利用Numeric

def optWith[T](a: Option[T])(op:(T,T)=>T) =
  (b:T) => a map (op(b,_)) getOrElse b

def restrict[T,FT,CT](x:T, floor:Option[FT], ceil:Option[CT])
  (implicit n:Numeric[T], fv:FT=>T, cv:CT=>T) =
  optWith(ceil map cv)(n.min) compose optWith(floor map fv)(n.max) apply(x)

我希望你喜欢类型签名:)

更新3:这是一个与边界相同的

def optWith[T, V <% T](op:(T,T)=>T)(a: Option[V]) =
  (b:T) => a map (op(b,_)) getOrElse b

def restrict[T:Numeric, FT <% T, CT <% T]
(floor:Option[FT], ceil:Option[CT], amt:T) = {
  val n = implicitly[Numeric[T]]; import n._
  optWith(min)(ceil) compose
  optWith(max)(floor) apply(amt)
}

如果没有别的...这很清楚地说明了为什么导入参数会是一件好事(tm)。想象一下,如果以下是有效代码:

def optWith[T, V <% T](op:(T,T)=>T)(a: Option[V]) =
  (b:T) => a map (op(b,_)) getOrElse b

def restrict[import T:Numeric,FT <% T,CT <% T]
(floor:Option[FT], ceil:Option[CT], amt:T) = {
  optWith(min)(ceil) compose
  optWith(max)(floor) apply(amt)
}

更新 4:在这里颠倒解决方案。这为未来的扩展提供了一些更有趣的可能性。

implicit def optRhs[T:Ordering](lhs:T) = new Object {
  val ord = implicitly[Ordering[T]]; import ord._

  private def opt(op: (T,T)=>T)(rhs:Option[T]) =
    rhs map (op(lhs,_)) getOrElse lhs

  def max = opt(ord.max) _
  def min = opt(ord.min) _
}

def restrict[T : Ordering](floor:Option[T], cap:Option[T], amt:T) =
  amt min cap max floor 

运气好的话,我会激励其他人用我的方法构建更好的解决方案。这些事情通常是这样解决的……

于 2010-11-10T15:03:30.107 回答
2

这在 Scalaz 中并不比在常规 Scala 中容易得多:

def restrict(floor: Option[Double], cap: Option[Double], amt: Double) =
  floor.map(amt max).orElse(Some(amt)).map(x => cap.map(x min).getOrElse(x)).get

(添加_之后maxmin如果它让你感觉更好,看看参数的去向。)

但是,一旦您了解了运算符的作用,Scalaz 就更容易阅读了。

于 2010-11-10T19:20:39.010 回答
2

I find that when a question asks to use an Option to indicate an optional parameter, there's usually a more natural way to represent the missing parameter. So I'm going to change the interface a little here, and use default arguments to define the function and named parameters to call the function.

def restrict(amt:Double,
            floor:Double = Double.NegativeInfinity,
            cap:Double=Double.PositiveInfinity):Double =
    (amt min cap) max floor

Then you can call:

restrict(6)
restrict(6, floor = 7)
restrict(6, cap = 5)

(Another example of the same principle.)

于 2010-11-14T04:25:49.887 回答
2

This is based on Ken Bloom's answer:

sealed trait Constrainer { def constrain(d : Double) : Double }

trait Cap extends Constrainer
trait Floor extends Constrainer
case object NoCap extends Cap { def constrain(d : Double) = d }
case object NoFloor extends Floor { def constrain(d : Double) = d }
implicit def d2cap(d : Double) = new Cap { def constrain(amt : Double) = d min amt }
implicit def d2floor(d : Double) = new Floor { def constrain(amt : Double) = d max amt }

def restrict(amt : Double, cap : Cap = NoCap, floor: Floor = NoFloor) : Double = {
  cap.constrain(floor.constrain(amt))
  //or (cap.constrain andThen floor.constrain) amt
}

It ends up with writing code like this:

restrict(amt, cap = 5D)
restrict(amt, floor = 0D)

I think that's pretty awesome and doesn't suffer from the problem with Ken's solution (in my opinion), which is that it is a hack!

于 2010-11-14T18:17:38.763 回答
1

This is another way to fix Landei's first answer

def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double = {
  val chopBottom = (floor.getOrElse(amt) max amt) 
  chopBottom min cap.getOrElse(chopBottom)
}
于 2010-11-14T04:15:36.560 回答
0

I'm adding another answer which was inspired by both retronym and Debilski - basically it amounts to converting the cap and floor to functions (Double => Double, if they are present) and then folding the identity function through them with composition:

def restrict(floor: Option[Double], cap: Option[Double], amt: Double) = {
  (identity[Double] _ /: List(floor.map(f => (_: Double) max f), cap.map(c => (_: Double) min c)).flatten){ _ andThen _ }(amt)
}
于 2010-11-15T08:34:50.853 回答
0

Straightforward solution with plain Scala and anonymous lambda, without any mappings, folds, Double.{Min/Max}Value, and so on:

def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double =
  ((x:Double) => x min cap.getOrElse(x))(amt max floor.getOrElse(amt))
于 2011-04-17T21:12:40.933 回答
0

I like the initial solution with the match-case most - beside the fact, that I didn't understand that amt means amount (in Germany, 'amt' means 'office') and I only knew cap as something I wear on my head ...

Now here is a really uninspired solution, using an inner method:

def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double = {
  def restrict (floor: Double, cap: Double, amt: Double) =
    (floor max amt) min cap
  var f = floor.getOrElse (amt)            
  val c = cap.getOrElse (amt)   
  restrict (f, c, amt) 
}
于 2011-08-06T03:11:21.217 回答