2012-01-18 21 views
4

我試圖將一些Haskell代碼轉換爲F#但我遇到了一些麻煩,因爲默認情況下Haskell是懶惰的,而F#不是。我還在學習圍繞F#的方式。下面是Haskell中的多態餘弦函數,具有相當不錯的性能。我想嘗試在F#中保持相同或更好的性能參數。我希望看到F#List版本和F#Seq版本,因爲Seq版本更像是懶惰的Haskell,但List版本可能會表現更好。謝謝你的幫助。將Haskell多態餘弦函數轉換爲F#

效率:算術運算次數使用比例方面的數目串聯

空間:使用常數空間,獨立的條款數的

takeThemTwoByTwo xs = 
    takeWhile (not . null) [take 2 ys | ys <- iterate (drop 2) xs] 

products xss = [product xs | xs <- xss] 

pairDifferences xs = 
    [foldr (-) 0 adjacentPair | adjacentPair <- takeThemTwoByTwo xs] 

harmonics x = [x/(fromIntegral k) | k <- [1 ..]] 

cosineTerms = scanl (*) 1 . products . takeThemTwoByTwo . harmonics 

cosine = foldl (+) 0 . pairDifferences . 
    take numberOfTerms . cosineTerms 
+0

哪個餘弦近似值是?輸入是「度數」還是「弧度」? – pad 2012-01-18 07:16:11

+0

這是弧度。 – 2012-01-18 18:49:14

+0

「相同或更好的性能參數」。如果你關心性能,爲什麼你使用這種算法? – 2012-06-15 12:28:08

回答

6

Pad的答案很好,但不是多態。一般來說,在F#中創建這樣的定義要比在Haskell中創建這樣的定義要少得多(並且有點痛苦)。下面是一個方法:

module NumericLiteralG = 
    let inline FromZero() = LanguagePrimitives.GenericZero 
    let inline FromOne() = LanguagePrimitives.GenericOne  

module ConstrainedOps = 
    let inline (~-) (x:^a) : ^a = -x 
    let inline (+) (x:^a) (y:^a) : ^a = x + y 
    let inline (*) (x:^a) (y:^a) : ^a = x * y 
    let inline (/) (x:^a) (y:^a) : ^a = x/y 

open ConstrainedOps 

let inline cosine n x = 
    let two = 1G + 1G 
    Seq.unfold (fun (twoIp1, t) -> Some(t, (twoIp1+two, -t*x*x/(twoIp1*(twoIp1+1G))))) (1G,1G) 
    |> Seq.take n 
    |> Seq.sum 
+0

如果沒有'NumericLiteralG'和'ConstrainedOps'模塊,通過指定參數'x'的類型和'cosine'的返回類型,你能不能更容易地做到這一點?我假設'ConstrainedOps'僅用於清理簽名,因爲數學運算已經是多態的。 – Daniel 2012-01-18 19:40:21

+0

@Daniel - 是的,''ConstrainedOps'只是爲了避免大量的中間類型變量(這只是醜陋的但不是有害的)。我認爲註釋參數和返回類型是不夠的 - 你也需要註釋混淆代碼的'two','twoIp1'等等。如果沒有'NumericLiteralG',您需要在幾個地方插入'GenericOne'('GenericZero'未使用並且可以被刪除)。但是,如果您要創建其他多態數字代碼,那麼這兩個模塊都可以重用,並使定義更具可讀性。 – kvb 2012-01-18 21:45:52

9

這裏是我的嘗試如果你是懶惰閱讀:

let harmonics x = 
    Seq.initInfinite(fun i -> - x*x/(float ((2*i+1)*(2*i+2)))) 

let cosineTerms = Seq.scan (*) 1.0 << harmonics 

let cosine numberOfTerms = Seq.sum << Seq.take numberOfTerms << cosineTerms 

我也很難找出您所使用的泰勒級數計算弧度cosine

cosine(x)= 1 - x /2! + x /4! - x /6! + ...

讓我描述你在做什麼:

  1. 創建的x/k無限序列,其中k1開始的整數。
  2. 拆分上述序列爲兩個組塊,並通過與的1種子相乘以具有x的序列掃描 /(第(2k-1)×(2K))(用在開始的1異常)。

  3. 拆分新序列爲兩個再次塊具有在x的形式4K-4 /差異(第(4k-4)!) - X 4K-2 /((4K-2) !)並將所有這些數字相加得到最終結果。

因爲它很可能是低效的分裂在F#序列和takeThemTwoByTwo功能不是必需的,我選擇另一種方法:

  1. 創建的無限序列 - X /((2K -1)*(2k))其中k是從1開始的整數。
  2. 通過乘以1的種子來掃描序列;我們得到一系列(-1)k * x 2k /((2k)!)。
  3. 總和所有元素以獲得最終結果。

上面的程序是我的描述的直接翻譯,簡潔而簡單。計算cosinenumberOfTerms = 200000迭代需要0。15秒鐘在我的機器上;我認爲它對於你的目的來說足夠高效。

此外,List版本應該很容易翻譯。

UPDATE:

好了,我的錯,是低估了問題的多態性一部分。我更關注性能部分。這裏是一個多態的版本(保持密切的float版本):

let inline cosine n (x: ^a) = 
    let one: ^a = LanguagePrimitives.GenericOne 
    Seq.initInfinite(fun i -> LanguagePrimitives.DivideByInt (- x*x) ((2*i+1)*(2*i+2))) 
    |> Seq.scan (*) one 
    |> Seq.take n 
    |> Seq.sum 

Seq.initInfinite比@kvb的回答Seq.unfold那麼強大。我保持它使事情變得簡單,因爲n無論如何都在int範圍內。

+0

嘿,謝謝,這是非常好的,簡潔:) – 2012-01-18 18:48:54

+0

@Downvoter:關心評論? – pad 2012-01-18 19:58:09

+0

[這是我。](http://www.youtube.com/watch?v=a43kowi2ncI)OP特別注意到了Haskell函數的多態性。你的不是多態的。 – Daniel 2012-01-18 20:06:48

3

作爲墊寫,這似乎是COS的泰勒級數展開(X)約X = 0:

餘弦(x)= 1 - X 2/2! + x 4/4! - x⁶/ 6! + ...

所以你的問題是一個XY問題:你提出了一個解決方案,而不是提出問題。提出問題反而更容易以不同的方式解決問題。

讓我們通過編寫F#中float特異性版本開始:

let cosine n x = 
    let rec loop i q t c = 
    if i=n then c else 
     loop (i + 1) (q + 10 + 8*i) (-t * x * x/float q) (c + t) 
    loop 0 2 1.0 0.0 

例如,我們可以計算爲x = 0.1的擴展1M方面:

cosine 1000000 0.1 

以最好的方式使F#中的這種多態是爲了將函數參數化爲它使用的運算符,並將其標記爲inline以便消除此參數化的性能開銷:

let inline cosine zero one ofInt (~-.) (+.) (*.) (/.) n x = 
    let rec loop i q t c = 
    if i=n then c else 
     loop (i + 1) (q + 10 + 8*i) (-.t *. x *. x /. ofInt q) (c +. t) 
    loop 0 2 one zero 

現在,我們可以計算出使用float這樣的,這是一樣的速度是以前1M方面:

cosine 0.0 1.0 float (~-) (+) (*) (/) 1000000 0.1 

但是,我們也可以做單精度float

cosine 0.0f 1.0f float32 (~-) (+) (*) (/) 1000000 0.1f 

和任意-precision rational:

cosine 0N 1N BigNum.FromInt (~-) (+) (*) (/) 10 (1N/10N) 

甚至符號:

type Expr = 
    | Int of int 
    | Var of string 
    | Add of Expr * Expr 
    | Mul of Expr * Expr 
    | Pow of Expr * Expr 

    static member (~-) f = Mul(Int -1, f) 
    static member (+) (f, g) = Add(f, g) 
    static member (*) (f, g) = Mul(f, g) 
    static member (/) (f, g) = Mul(f, Pow(g, Int -1)) 

cosine (Int 0) (Int 1) Int (~-) (+) (*) (/) 3 (Var "x") 

爲了使它更快,扯起公共部分-x*xloop