2010-11-10 46 views
10

我試圖做盡可能少的代碼儘可能以下,並在功能上儘可能:Scala的函數式編程體操

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

什麼是「限制」功能,該做的? – OscarRyz 2010-11-10 15:22:39

+0

對不起 - 它必須根據提供的'amt'返回一個Double,但是它是由可選參數設置的上限或下限。也就是說,如果提供了10但是Some(8)的上限,該方法應該返回8 – 2010-11-10 18:07:36

回答

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程序員,一個可能不知道的特殊的斯卡拉斯運營商和使用的方法(|>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 ...?

+2

您值得擁有所有要點 - 中間答案充滿了迷人之處。可能是一個想法來闡明發生了什麼 – 2010-11-10 15:20:23

+1

備受期待的scalaz澄清庫仍然是一個正在進行的工作。作爲一個潛行預覽,儘管......我相信大多數情況下它是基於一個鮮爲人知的數學證明,它證明了源代碼和明確描述之間的同態性:) – 2010-11-10 15:38:08

+0

事情是,一旦你理解了'|>的類型簽名, ',這很明顯,是嗎? – 2010-11-10 15:50:14

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

看來我的口味有點太 「lispy」,但通過測試:-)

[第二編輯]

第一個版本也可以「修復」:

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

這實際上與Dave Griffith給出的確切答案相同 - 它不起作用 – 2010-11-10 15:10:20

+0

「2nd Edit」版本很棒 – 2010-11-14 04:12:22

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

UPDATE 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 

如果運氣好的話,我會激勵別人去建立一個更好的解決方案。這就是如何將這些東西平時工作了...

+0

嗯,它但我想要最少的代碼行 – 2010-11-10 15:16:17

+0

更新4中的很好的解決方案。非常荒謬的是,用一種語言解決這個問題有多少種不同的方法。這個問題只是不會被問到Java! – 2010-11-14 19:41:04

+0

True ...在Java中,您只需進行明確的空檢查,可能會造成NPE風險,並且必須爲可能最終使用的每個基元重載N次。即使如此,它仍然不會成爲您可能關心的任何其他訂購類型的有效代碼... – 2010-11-14 22:19:48

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

但我有我在這裏失去了一些機會的感覺,所以我可能無法完成。

15

沒有這麼簡潔的scalaz版本,但在另一方面,沒有依賴性,

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

我愛這個。到目前爲止,所有其他解決方案都迫使你理解兩個測試的作用,因爲這個解決方案選擇了地板和蓋子之間的中間位置。 – huynhjl 2010-11-11 03:21:41

2

這不是Scalaz比普通斯卡拉真的要容易得多:

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之後加上_,如果它讓你感覺更好看看參數在哪裏去)

斯卡拉斯有點容易重新但是,一旦你瞭解運營商的行爲,

+0

發現最好的解決方案(至少在我看來)是使用'/:'從Debilski – 2010-11-14 19:45:41

5

而不是純粹簡潔,這表明如果您將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.mapFunction1.andThen的一種方式;和OptionW#|,這是Option.getOrElse的別名。

UPDATE

這就是我一直在尋找:

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] = [email protected] 

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,有兩個方向的隱式轉換。有一個Monoid的實例定義爲Endo[A],Monoid#plus鏈接函數,而Monoid#zero返回標識函數。如果我們有一個ListEndo[A],我們可以將列表和結果相加成單個值,可以用作A => A

MA#foldMap將給定函數映射到Foldable數據類型上,並將其與Monoid簡化爲單個值。在此之上,foldMapEndo是一種便利。這種抽象允許您輕鬆地將Option中的頂蓋和底板更改爲任何可摺疊類型,例如List

val capAndFloor = Seq(foldMapEndo(List(1, 2, max), foldMapEndo(cap, min)).collapsee 
capAndFloor: scalaz.Endo[Int] = [email protected] 

另一個重構可能會導致:

val capAndFloor = Seq((cap, min), (floor, max)).foldMap { case (a, f) => foldMapEndo(a, f) } 
capAndFloor: scalaz.Endo[Int] = [email protected] 
+1

一如既往的棒!如果我想用其他函數或在代碼庫的其他地方編寫cap/floor,這種方法顯然很有用 - 它實際上只在一個小地方 – 2010-11-11 22:13:13

+0

該方法與Kevin和Daniel的答案非常相似。看到事情變得更容易與curried函數一起探索,這很有趣。我仍然想知道是否存在隱藏在「或」身份背後的抽象。 – retronym 2010-11-11 22:17:14

+0

我認爲你可以使用'| + |'而不是'List.sum'方法? – 2010-11-11 23:41:12

1

這是另一種方式來解決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) 
} 
2

我發現,當一個問題問到使用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) 

Another example of the same principle.

+0

我不確定我是否同意你的意見;如何使用比表達事實「真相」更自然的「詭計」?也就是說,呼叫站點語法非常好!我要添加一個基於你的解決方案的答案,我認爲這更合適 – 2010-11-14 18:11:00

+0

@oxbow_lakes,如何使用無限來表示「無限制」的訣竅?那怎麼不是事實? (我認爲,如果你要反對,這將是一個反對界面的變化,而不是無限的使用作爲限制。) – 2010-11-14 21:52:38

+0

我明白你的觀點;但是「無限制」和「無限極限」之間有一個區別(至少在我的腦海裏)。讓我們面對它,「Double.PositiveInfinity」是一個破解! – 2010-11-14 22:14:25

2

這是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 
} 

它與寫這樣的代碼結束

我認爲這是非常棒的,不會遇到肯解決方案的問題(在我看來),這是一個黑客

+0

是的。我認爲密封的層次結構和單體對象有些被忽視。他們通常對圖書館沒什麼意義,但他們在商業規則上經常表現得很好。 – 2010-11-17 01:40:50

0

我添加其通過兩個返璞詞Debilski啓發另一個答案 - 基本上它相當於轉換帽和地板函數(Double => Double,如果它們存在的話),然後通過它們摺疊所述恆等函數用組合物:

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

用普通Scala和匿名拉姆達,沒有任何映射,褶皺,雙{最小值/最大值}值,等等直接的解決方案:

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

我最喜歡最初的解決方案 - 除了事實,我不明白,amt意味着amount(在德國,'amt'的意思是'辦公室'),我只知道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) 
}