2016-07-07 58 views
4

玩F#,我试图以更实用的方式思考代码。我的大部分工作本质上都是数字化的,所以我正在考虑这种再教育是否有意义。是以一种功能性的方式编写数字代码,例如试图将一个方形钉固定在一个圆孔中,或者仅仅是一个陡峭的学习曲线问题,而不考虑应用程序?功能化数字代码

例如,让我们这表明大量的弱法的一个片段:

open System 
open System.IO 
open System.Windows.Forms 
open System.Windows.Forms.DataVisualization 
open FSharp.Data 
open FSharp.Charting 
open FSharp.Core.Operators 
open MathNet.Numerics 
open MathNet.Numerics.LinearAlgebra 
open MathNet.Numerics.Random 
open MathNet.Numerics.Distributions 
open MathNet.Numerics.Statistics 


let T = 1000 

let arr1 = Array.init T (fun i -> float i*0.) 
for i in 0 .. T-1 do 
    arr1.[i] <- [|for j in 1..i do yield Exponential.Sample(0.1)|] |> Statistics.Mean 

let arr2 = Array.init T (fun i -> float i*0.) 
for i in 0 .. T-1 do 
    arr2.[i] <- arr1.[1 .. i] |> Statistics.Mean 

arr2 |> Chart.Line |> Chart.Show 

是否有表达上述的简洁实用的方式?有多少功能范式可以被纳入到这样的工作中?

不确定问题是否为脱离主题。谢谢。

+1

顺便说一句,有科学家的书_F#,尽管有点过时。还有[真实世界函数式编程]摘录(https://code.msdn.microsoft.com/Chapter-4-Numerical-3df3edee)。也许更近期的书对数学网有更好的解释。 – s952163

回答

5

我先不分开呼叫Array.init并设置初始值。您可以使用形式@ s952163使用他们的答案,或者根据您的代码:这个

let arr1 = Array.init T (fun i -> 
    [|for j in 1..i do yield Exponential.Sample 0.1 |] |> Statistics.Mean 
) 

问题是,你正在分配中间阵列,这是昂贵的 - 你计算后立即反正丢弃意思。替代:

let arr1 = Array.init T (fun i -> 
    Exponential.Samples 0.1 |> Seq.take (i+1) |> Seq.average 
) 

现在用于第二部分:您正在1..i反复计算元素的平均值,这成为一个为O(n^2)的操作。你可以用O(n)中的元素1..i加上第i个元素的和来解决它。

let sums, _ = 
    arr1 
    |> Array.mapFold (fun sumSoFar xi -> 
     let s = sumSoFar + xi 
     s, s 
    ) 0.0 
let arr2 = 
    sums 
    |> Array.mapi (fun i sumi -> sumi/(float (i + 1))) 

当然,你可以全部在一个管道中写入。

或者,使用库函数Array.scan来计算累计总和,这将在这种情况下,给你长度T+1的结果,从中你然后删除第一个元素:

let arr2 = 
    Array.sub (Array.scan (+) 0.0 arr1) 1 T 
    |> Array.mapi (fun i sumi -> sumi/(float (i + 1))) 

或避免中间阵列:

Seq.scan (+) 0.0 arr1 
|> Seq.skip 1 
|> Seq.mapi (fun i sumi -> sumi/(float (i + 1))) 
|> Seq.toArray 
+0

是的!我正在等待somethig与折叠:) – s952163

2

我认为这是一个很好的问题。我的印象是,编写函数式数字代码(想想Matlab与Mathematica)时遇到的麻烦不在于语法,而在于性能。但同时它也很容易并行化代码。

我会这样写代码:

let arr1' = [|for i in 0..1000 -> Array.init i (fun i -> Exponential.Sample(0.1)) |> Statistics.Mean |]

注意到一个)没有可变分配,B)没有索引和c)我不初始化0基于阵列并填写而是用函数初始化数组。

我还会调查是否可以直接使用Exponential.Sample生成样本,而不是调用它1000次。

编辑

喜欢这个:Exponential.Samples(0.1) |> Seq.take 1000

而且基于以下@ChristophRüegg的评论:

let expoMean (x:float []) = 
    Exponential.Samples(x,0.1) 
    x |> Statistics.Mean 

Array.init 1000 (fun _ -> Array.replicate 1000 0. |> expoMean) 

我没有这个基准。

+1

内部并行化的最快方法是创建一个数组,然后将其传递给静态的'Exponential.Samples(array,0.1)'来填充它。 –

+0

@ChristophRüegg有趣。谢谢! – s952163

6

这些实际上两个问题:一个关于改善给定的代码和一个约在F#官能数字代码。由于其他答案已经集中在特定的代码上,因此我将重点讨论更一般的问题。

它是关于性能吗?

根据我的经验,函数式编程在数值上的适用性取决于性能要求。执行时间越重要,你越想在功能风格上妥协。

如果性能不是问题,功能代码往往工作得很好。它简洁而安全,比命令式编程更接近数学写作。当然,有些问题很好地映射到命令式程序,但总体而言,功能风格是一个很好的默认选择。

如果性能有点问题,您可能想要在不变性方面妥协。 F#中函数代码的主要代价来自垃圾回收器,尤其是来自中间生命周期的对象。使昂贵的对象变为可变并重新使用它们可以在执行速度上产生巨大的差异。如果您想以简洁安全的方式编写像流体动力学,n体模拟或游戏这样的东西,但不是针对踏板到金属的执行速度,那么多参数F#风格可能是一种很好的方法走。

如果性能是一切,那么很可能是,无论如何您都希望GPU执行。或者可以充分利用CPU矢量单元,多线程等。虽然有人试图在GPU上使用F#,但这种语言并不是为了不惜一切代价而加速设计的。在这种情况下使用更接近硬件的语言可能会更好。

当问题是这些问题的混合时,通常可以混合解决方案。例如,昨天我需要对一组图像执行每像素计算,并且执行时间非常重要。因此,我使用.NET库在F#中读取图像,然后将它们与GLSL计算着色器一起上传到GPU,然后将像素转换为“F#land”。这里的要点是管理操作效率不高;该代码仍然没有真正的原因复制东西。但是这只是一次操作,可能会吃掉所有的性能,所以在一次操作中使用高性能的工具是合理的,而所有其他操作在F#中整齐安全地进行。

+2

很高兴有人选择详细地采取这部分的问题。我还想补充一点,你可以沿着不同的路线去改善性能。当谈到小恒定因子加速时,命令式代码总是会赢,但如何使用更抽象的功能性代码来设计算法更复杂的算法通常更容易和更明显。出于这个原因,我认为对于已经解决的问题,你已经证明了一个必要的解决方案,但是可能会更好地为新的开发提供更多功能的代码。 – TheInnerLight