问题描述
玩F#,我试图以更有效的方式来思考代码。我的大部分工作本质上都是数字化的,所以我正在考虑这种再教育是否有意义。是否以一种功能性的方式编写数字代码,例如试图将一个方格钉固定在一个圆孔中,或者仅仅是一个陡峭的学习曲线问题,而不管应用程序如何?例如,让我们看一个演示大数的弱定律的片段:
打开系统
打开System.IO
打开System.Windows.Forms
打开System.Windows.Forms.DataVisualization
打开FSharp.Data
打开FSharp.Charting
打开FSharp.Core.Operators
打开MathNet.Numerics
打开MathNet.Numerics.LinearAlgebra
打开MathNet.Numerics.Random
打开MathNet.Numerics.Distributions
打开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 1 in ..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
有没有一种简洁的表达上述功能的方法?有多少功能范式可以被纳入到这样的工作中?
不确定问题是否为脱离主题。感谢。
我首先不会将调用分开到 Array.init
并设置初始值。您可以使用@ s952163在其答案中使用的表单,或者基于您的代码:
let arr1 = Array.init T (fun i - >
[| for 1 in ..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)操作。你可以通过使用元素1..i之和是元素1 .. {i-1}加上i.th元素的总和这一事实,在O(n)中解决它。
let sums,_ =
arr1
|> Array.mapFold(fun sumSoFar xi - >
let s = sumSoFar + xi
s,s
)0.0
let arr2 =
summs
|> ; 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
Playing with F#, I'm trying to think of code in a more functional way. A large part of my work happens to be numerical in nature so I'm thinking whether this re-education makes sense. Is writing numerical code in a functional way like trying to fit a square peg in a round hole or is it simply a matter of a steep learning curve irrespective of the application?
For example, lets take a snippet which demonstrates the weak law of large numbers:
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
Is there a succinct functional way of expressing the above? How much of a functional paradigm can be incorporated into work like this?
Not sure if the question is off topic. Thanks.
I'd first not separate the call to Array.init
and setting the initial values. You can use the form that @s952163 use in their answer, or based on your code:
let arr1 = Array.init T (fun i ->
[|for j in 1..i do yield Exponential.Sample 0.1 |] |> Statistics.Mean
)
Issue with this is that you are allocating intermediate arrays, which is costly - and you anyway discard them right after computing the mean. Alternative:
let arr1 = Array.init T (fun i ->
Exponential.Samples 0.1 |> Seq.take (i+1) |> Seq.average
)
Now for the second part: You are computing the mean of elements 1..i repeatedly, which becomes an O(n^2) operation. You can solve it in O(n) by using the fact that the sum of elements 1..i is the sum of elements 1..{i-1} plus the i.th element.
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)))
Of course you can all write that in a single pipe.
Alternatively, use the library function Array.scan
to compute the cumulative sums, which would in this case give you a result of length T+1
, from which you'd then drop the first element:
let arr2 =
Array.sub (Array.scan (+) 0.0 arr1) 1 T
|> Array.mapi (fun i sumi -> sumi / (float (i + 1)))
or avoid the intermediate arrays:
Seq.scan (+) 0.0 arr1
|> Seq.skip 1
|> Seq.mapi (fun i sumi -> sumi / (float (i + 1)))
|> Seq.toArray
这篇关于使数值代码功能化的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!