Scala 函数式编程体操

发布于 2024-10-02 10:08:18 字数 1887 浏览 7 评论 0原文

我试图用尽可能少的代码和尽可能多的功能来执行以下操作:

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

守望孤独 2024-10-09 10:08:18

编辑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)

请参阅 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 中拥有了这个……?

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…?

椵侞 2024-10-09 10:08:18

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

List(floor.getOrElse(Double.NegativeInfinity), cap.getOrElse(Double.PositiveInfinity), amt).sorted.apply(1)

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)
柠栀 2024-10-09 10:08:18

我将从这个开始:

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

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

I'll start with this:

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

But I have the feeling I'm missing some opportunity here, so I may not be finished.

抱着落日 2024-10-09 10:08:18

这表明,如果将 capfloor 转换为函数,组合会变得多么容易,而不是纯粹的简洁。

scala> val min = (scala.math.min _).curried                                        
min: (Int) => (Int) => Int = <function1>

scala> val max = (scala.math.max _).curried                                        
max: (Int) => (Int) => Int = <function1>

scala> def orIdentity[A](a: Option[A])(f: A => A => A): (A => A) = a ∘ f | identity
orIdentity: [A](a: Option[A])(f: (A) => (A) => A)(A) => A

scala> val cap = 5.some; val floor = 1.some                                        
cap: Option[Int] = Some(5)
floor: Option[Int] = Some(1)

scala> val ffloor = orIdentity(floor)(max)                                         
ffloor: (Int) => Int = <function1>

scala> val fcap = orIdentity(cap)(min)                                             
fcap: (Int) => Int = <function1>

scala> val capAndFloor = fcap ∘ ffloor                                             
capAndFloor: (Int) => Int = <function1>    

scala> (0 to 8).toSeq ∘ (capAndFloor)    
res0: Seq[Int] = Vector(1, 1, 2, 3, 4, 5, 5, 5, 5)

在 scalaz 中,我使用函子映射 MA#∘ 来作为使用 Option.map 和 Function1.andThen 的一种方式;和 OptionW#|,它是 Option.getOrElse 的别名。

更新

这就是我一直在寻找的:

scala> import scalaz._; import Scalaz._
import scalaz._
import Scalaz._

scala> val min = (scala.math.min _).curried                                        
min: (Int) => (Int) => Int = <function1>

scala> val max = (scala.math.max _).curried                                        
max: (Int) => (Int) => Int = <function1>

scala> def foldMapEndo[F[_]: Foldable, A](fa: F[A], f: A => A => A): Endo[A] = 
     |    fa.foldMap[Endo[A]](a => f(a))                                       
foldMapEndo: [F[_],A](fa: F[A],f: (A) => (A) => A)(implicit evidence$1: scalaz.Foldable[F])scalaz.Endo[A]

scala> val cap = 5.some; val floor = 1.some                                    
cap: Option[Int] = Some(5)
floor: Option[Int] = Some(1)    

scala> val capAndFloor = List(foldMapEndo(floor, max), foldMapEndo(cap, min)) ∑
capAndFloor: scalaz.Endo[Int] = scalaz.Endos$anon$1@4352d1fc

scala>(0 to 10).toSeq.map(capAndFloor)                                               
res0: Seq[Int] = Vector(1, 1, 2, 3, 4, 5, 5, 5, 5, 5, 5)

scalaz.Endo[A]A => 的包装器。 A,双向都有隐式转换。有一个为 Endo[A] 定义的 Monoid 实例,Monoid#plus 链接函数,Monoid#zero code> 返回恒等函数。如果我们有一个 Endo[A]List,我们可以对列表求和并得出单个值,该值可以用作 A =>一个。

MA#foldMap 将给定函数映射到 Foldable 数据类型,并使用 Monoid 简化为单个值。 foldMapEndo 在此之上提供了便利。这种抽象允许您轻松地将证明 Option 中的上限和下限更改为任何可折叠类型,例如 List

val capAndFloor = Seq(foldMapEndo(List(1, 2, max), foldMapEndo(cap, min)).collapsee
capAndFloor: scalaz.Endo[Int] = scalaz.Endos$anon$1@76f40c39

另一次重构可能会导致:

val capAndFloor = Seq((cap, min), (floor, max)).foldMap { case (a, f) => foldMapEndo(a, f) }
capAndFloor: scalaz.Endo[Int] = scalaz.Endos$anon$1@25b85c8e

Rather than going for pure brevity, this shows how much easier composition becomes if you turn cap and floor into functions.

scala> val min = (scala.math.min _).curried                                        
min: (Int) => (Int) => Int = <function1>

scala> val max = (scala.math.max _).curried                                        
max: (Int) => (Int) => Int = <function1>

scala> def orIdentity[A](a: Option[A])(f: A => A => A): (A => A) = a ∘ f | identity
orIdentity: [A](a: Option[A])(f: (A) => (A) => A)(A) => A

scala> val cap = 5.some; val floor = 1.some                                        
cap: Option[Int] = Some(5)
floor: Option[Int] = Some(1)

scala> val ffloor = orIdentity(floor)(max)                                         
ffloor: (Int) => Int = <function1>

scala> val fcap = orIdentity(cap)(min)                                             
fcap: (Int) => Int = <function1>

scala> val capAndFloor = fcap ∘ ffloor                                             
capAndFloor: (Int) => Int = <function1>    

scala> (0 to 8).toSeq ∘ (capAndFloor)    
res0: Seq[Int] = Vector(1, 1, 2, 3, 4, 5, 5, 5, 5)

From scalaz, I use MA#∘, the functor map, both as a way of using Option.map and Function1.andThen; and OptionW#| which is an alias for Option.getOrElse.

UPDATE

This is what I was looking for:

scala> import scalaz._; import Scalaz._
import scalaz._
import Scalaz._

scala> val min = (scala.math.min _).curried                                        
min: (Int) => (Int) => Int = <function1>

scala> val max = (scala.math.max _).curried                                        
max: (Int) => (Int) => Int = <function1>

scala> def foldMapEndo[F[_]: Foldable, A](fa: F[A], f: A => A => A): Endo[A] = 
     |    fa.foldMap[Endo[A]](a => f(a))                                       
foldMapEndo: [F[_],A](fa: F[A],f: (A) => (A) => A)(implicit evidence$1: scalaz.Foldable[F])scalaz.Endo[A]

scala> val cap = 5.some; val floor = 1.some                                    
cap: Option[Int] = Some(5)
floor: Option[Int] = Some(1)    

scala> val capAndFloor = List(foldMapEndo(floor, max), foldMapEndo(cap, min)) ∑
capAndFloor: scalaz.Endo[Int] = scalaz.Endos$anon$1@4352d1fc

scala>(0 to 10).toSeq.map(capAndFloor)                                               
res0: Seq[Int] = Vector(1, 1, 2, 3, 4, 5, 5, 5, 5, 5, 5)

scalaz.Endo[A] is a wrapper around A => A, there are implicit conversions in both directions. There is an instance of Monoid defined for Endo[A], Monoid#plus chains the functions, and Monoid#zero returns the identity function. If we have a List of Endo[A], we can sum the list and result in a single value, which can be used as A => A.

MA#foldMap maps the given function over a Foldable data type, and reduces to a single value with a Monoid. foldMapEndo is a convenience on top of this. This abstraction allows you to easily change from proving the cap and floor in Options to any foldable type, such as a List.

val capAndFloor = Seq(foldMapEndo(List(1, 2, max), foldMapEndo(cap, min)).collapsee
capAndFloor: scalaz.Endo[Int] = scalaz.Endos$anon$1@76f40c39

Another refactoring might lead to:

val capAndFloor = Seq((cap, min), (floor, max)).foldMap { case (a, f) => foldMapEndo(a, f) }
capAndFloor: scalaz.Endo[Int] = scalaz.Endos$anon$1@25b85c8e
绅刃 2024-10-09 10:08:18

这个怎么样?

//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))

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

[第二次编辑]

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

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

How about this?

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

[Edit]

Second try:

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

Looks a little bit too "lispy" for my taste, but passes the tests :-)

[2nd Edit]

The first version can be "repaired", too:

def restrict(floor: Option[Double], cap: Option[Double], amt: Double): Double =
  (floor.getOrElse(-Double.MaxValue) max amt) min cap.getOrElse(Double.MaxValue)
嗫嚅 2024-10-09 10:08:18

没有更漂亮,没有更短,当然也没有更快!
但它更可组合,更通用,更“功能”:

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

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:还有这个版本,更好地利用数字

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

如果没有别的......这很清楚地表明了原因导入参数将是一件好事。想象一下,如果以下是有效的代码:

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 

如果幸运的话,我将激励其他人利用我的解决方案构建更好的解决方案。这些事情通常都是这样进行的...

Not prettier, not much shorter, and certainly not faster!
But it is more composable more generic and more "functional":

EDIT: made the code fully generic :)

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)

UPDATE 2: There's also this version, taking better advantage of 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)

I hope you like type signatures :)

UPDATE 3: Here's one that does the same with bounds

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

If nothing else... this shows quite clearly why import parameters would be a Good Thing(tm). Imagine if the following was valid code:

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

UPDATE 4: Turning the solution upside down here. This one offers some more interesting possibilities for future extension.

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 

With any luck, I'll inspire someone else to build a better solution out of mine. That's how these things usually work out...

紙鸢 2024-10-09 10:08:18

这在 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 就更容易阅读了。

This isn't really much easier in Scalaz than in regular 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

(Add _ after max and min if it makes you feel better to see where the parameter goes.)

Scalaz is a little easier to read, though, once you understand what the operators do.

背叛残局 2024-10-09 10:08:18

我发现当问题要求使用 Option 来指示可选参数时,通常有一种更自然的方式来表示缺少的参数。因此,我将在这里稍微更改一下接口,并使用默认参数来定义函数并使用命名参数来调用该函数。

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

然后你可以调用:(

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

相同原理的另一个示例。

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.)

失而复得 2024-10-09 10:08:18

这是基于 Ken Bloom 的答案

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
}

最终会编写这样的代码:

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

我认为这非常棒,并且不会遇到 Ken 解决方案的问题(在我看来),即它是一个黑客

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!

拥有 2024-10-09 10:08:18

这是修复 Landei 的第一个答案的另一种方法

def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double = {
  val chopBottom = (floor.getOrElse(amt) max amt) 
  chopBottom min cap.getOrElse(chopBottom)
}

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)
}
寻梦旅人 2024-10-09 10:08:18

我添加了另一个答案,该答案的灵感来自于 retronymDebilski - 基本上相当于将上限和下限转换为函数 (Double => Double< /code>,如果它们存在),然后通过它们组合折叠恒等函数:

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

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)
}
无边思念无边月 2024-10-09 10:08:18

使用普通 Scala 和匿名 lambda 的简单解决方案,没有任何映射、折叠、Double.{Min/Max}Value 等:

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

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))
虫児飞 2024-10-09 10:08:18

我最喜欢匹配大小写的初始解决方案 - 除此之外,我不明白 amt 意味着 amount (在德国,'amt' 意味着 'office '),我只知道 cap 是我戴在头上的东西......

现在这是一个非常平庸的解决方案,使用内部方法:

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

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) 
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文