2016-07-07 61 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