如何重构代码以使其具有功能性样式? [英] How to refactor code to make it functional style?
问题描述
使用F#,我试图以一种更实用的方式来思考代码.我工作的很大一部分恰好是数字性质的,所以我在考虑这种再教育是否有意义.编写数字代码的功能是否像尝试将方形钉插入圆孔中一样,还是仅仅是陡峭的学习曲线问题,而不管其用途如何?
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?
推荐答案
我首先不会将对Array.init
的调用和设置初始值分开.您可以使用@ s952163在其答案中使用的形式,也可以根据您的代码使用
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
)
现在第二部分:您正在重复计算元素1..i的平均值,这将成为O(n ^ 2)运算.您可以通过使用元素1..i的总和是元素1 ... {i-1}加上第i.th个元素的事实,在O(n)中解决该问题.
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.
或者,使用库函数Array.scan
计算累积总和,在这种情况下,您将得到长度为T+1
的结果,然后从该结果中删除第一个元素:
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)))
或避免使用中间数组:
Seq.scan (+) 0.0 arr1
|> Seq.skip 1
|> Seq.mapi (fun i sumi -> sumi / (float (i + 1)))
|> Seq.toArray
这篇关于如何重构代码以使其具有功能性样式?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!