繁体   English   中英

将Haskell多态余弦函数转换为F#

[英]Converting Haskell Polymorphic Cosine function to F#

我正在尝试将一些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

如果你懒得阅读,这是我的尝试:

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 - X2 分之2! + X 4月4日 - X6 分之6! + ...

让我来描述你在做什么:

  1. 创建一个无限的x/k序列,其中k是从1开始的整数。
  2. 将上面的序列拆分成两个块,并通过乘以1的种子进行扫描,得到x 2 /((2k-1)*(2k))的序列(开头的1除外)。

  3. 将新序列再次分成两个块,以x 4k-4 /((4k-4)!) - x 4k-2 /((4k-2)!)的形式存在差异,并将它们全部相加得到最后结果。

因为在F#中拆分序列可能效率低下并且takeThemTwoByTwo函数不是必需的,我选择了另一种方法:

  1. 创建无限序列 - x 2 /((2k-1)*(2k))其中k是从1开始的整数。
  2. 通过乘以1的种子扫描序列; 我们得到一个(-1) k * x 2k /((2k)!)的序列。
  3. 对所有元素求和以获得最终结果。

以上程序是我的描述的直接翻译,简洁明了。 使用numberOfTerms = 200000次迭代计算cosine需要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

Seq.initInfinite是小于强大Seq.unfold在@kvb的答案。 我保持简单,因为无论如何n都在int范围内。

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM