我正在尝试将一些Haskell代码转换为F#,但我遇到了一些麻烦,因为Haskell默认是懒惰而F#不是。我还在学习F#的方式。下面是Haskell中的多态余弦函数,具有相当好的性能。我想尝试在F#中保持相同或更好的性能参数。我希望看到一个F#List版本和一个F#Seq版本,因为Seq版本更像是懒惰的Haskell,但List版本可能会表现得更好。谢谢你的帮助。
效率:使用的算术运算数与串联项数成比例
空间:使用恒定空间,与术语数无关
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 :(得分: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
:
余弦(x)= 1 - x 2 / 2! + x 4 / 4! - x 6 / 6! + ...
让我描述你在做什么:
x/k
的无限序列,其中k
是一个从1
开始的整数。将上面的序列分成两个块并通过乘以1
的种子进行扫描,得到x 2 /((2k-1)*(2k)的序列))(开头的1
除外)。
将新序列再次分成两个块,形成x 4k-4 /((4k-4)!) - x 4k-2 /((4k-2)!)并总结所有这些以获得最终结果。
因为在F#中分割序列可能效率低下而且takeThemTwoByTwo
函数不是必需的,所以我选择了另一种方法:
k
是一个从1
开始的整数。1
的种子扫描序列;我们得到一个(-1) k * x 2k /((2k)!)的序列。以上程序是我的描述的直接翻译,简洁明了。使用cosine
次迭代计算numberOfTerms = 200000
在我的机器上需要0.15秒;我认为它足以满足您的目的。
此外,List
版本应该很容易从这个版本翻译。
<强>更新强>
好的,我的错是低估了问题的多态性部分。我更关注性能部分。这是一个多态版本(与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
在@kvb的回答中, Seq.initInfinite
的功能不如Seq.unfold
。我保持简单,因为n
无论如何都在int
范围内。
答案 1 :(得分: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
答案 2 :(得分:3)
正如Pad所写,这似乎是cos( x )关于 x = 0的泰勒级数展开:
余弦(x)= 1 - x²/ 2! +x⁴/ 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
任意精确理性:
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*x
中提升公共子表达式loop
。