F# 惯用F-简单统计函数

F# 惯用F-简单统计函数,f#,idioms,F#,Idioms,作为一个简单挑战的一部分,我将从头开始编写两个简单的统计函数,并尝试以最惯用的方式编写它们。我对函数式编程相当陌生,所以我希望从一开始就学习如何创建简单的东西 以下是我目前掌握的情况: let mean (x : float list) : float = (List.sum x) / (float (List.length x)) let variance (x : float list) : float = x |> List.map (fun a ->

作为一个简单挑战的一部分,我将从头开始编写两个简单的统计函数,并尝试以最惯用的方式编写它们。我对函数式编程相当陌生,所以我希望从一开始就学习如何创建简单的东西

以下是我目前掌握的情况:

let mean (x : float list) : float =
    (List.sum x) / (float (List.length x))

let variance (x : float list) : float =
    x
    |> List.map (fun a -> pown (a - (mean x)) 2)
    |> mean

let stdDev =
    variance >> Math.Sqrt
我喜欢使用composition来定义stdDev函数,但我觉得可能有更漂亮、更惯用的方法来定义前两个函数


有什么建议吗?

您的代码非常好,非常地道

就我个人而言,只要可能,我更喜欢一行。通过这种方式,我可以对齐代码以突出显示函数之间的相似性和差异。模式就是这样跳到你身上的

let mean     x = (Seq.sum x) / (float (Seq.length x))
let variance x = let m = mean x
                 x |> Seq.map (fun a -> pown (a - m) 2) |> mean
let stdDev   x = x |> variance |> Math.Sqrt
我也更喜欢seq而不是list,因为它们可以与列表、数组、集合或任何其他序列一起使用

do  [| 5. ; 6. ; 7. |] |> stdDev |> printfn "%A"
do  [  5. ; 6. ; 7.  ] |> stdDev |> printfn "%A"    
Set [  5. ; 6. ; 7.  ] |> stdDev |> printfn "%A"
seq [  5. ; 6. ; 7.  ] |> stdDev |> printfn "%A"        
seq {  5.   ..   7.  } |> stdDev |> printfn "%A"        
在F中,最好避免使用>>复合运算符,而是使用管道|>。
这样组合函数有很多问题。例如,上面的代码不可能使用不同的类型,如列表和数组。

一个值得做的小改动是从方差函数的lambda函数体中提取mean x调用。F编译器可能不会自动为您执行此操作,因此您最终会重新计算列表中每个元素的平均值:

let variance (x : float list) : float =
    let mx = mean x
    x
    |> List.map (fun a -> pown (a - mx) 2)
    |> mean

作为另一个回答中提到的Amieles,你也可以考虑使用不同的类型而不是列表。列表很好而且很实用,但是Seq将使代码与任何集合一起工作。或者,如果使用更大的数据进行计算,数组可能会快一点。

函数组合操作符没有它的声誉那么差,它只需要稍微小心,不要遇到或遇到任何相关问题。同名错误FS0030表示:

使“stdDev”的参数显式,如果不显式,则 要使其成为泛型,请添加类型注释

我们还可以添加一个类型注释,以使let绑定值比以其他方式推断的值更通用

let mean : seq<_> -> _ = 
    Seq.fold (fun (s, l) t -> s + t, l + 1) (0., 0) >> function
    | _, 0 -> failwith "empty collection"
    | s, l -> s / float l
let variance x = x |> Seq.map (x |> mean |> (-) >> fun a -> a * a) |> mean
let stdDev : seq<_> -> _ = variance >> sqrt
[5. ; 6. ; 7.] |> stdDev |> printfn "%A"    // prints 0.8164965809
{5.   ..   7.} |> stdDev |> printfn "%A"    // prints 0.8164965809

如果没有注释,mean和stdDev都不会在这里编译,因为值限制,它们不会在模块中被调用。即使如此,它们也仅限于实现System.Collections.Generic.IEnumerableGood调用的第一种类型。我刚刚在[1.0..100000.0]上运行了两个版本,得到了:Real:00:06:29.198,CPU:00:00:43.546,GC gen0:1,gen1:0,gen2:0和Real:00:00:00.050,CPU:00:00:00.031,GC gen0:1,gen1:1,gen2:0。关于选择类型,你有什么建议?对于通用代码使用Seq,对于更具体的用例使用List&Array?我以前只在处理惰性评估序列时使用过Seq…感谢您的回答!这里有很多有用的东西。如果由于操作s/float l,mean必须始终采用浮点数,那么保持let界限值更一般有什么意义?另外,在组合函数上,我们实际上是在执行meanX-x^2,对吗?我知道这对这个例子来说并不重要,但对于管道来说,操作数的顺序是我一直在努力解决的问题。确保x-meanX的正确方法是否类似于x |>Seq.map x |>mean |>~-|>+?这看起来很混乱…好的。别介意第一个问题。我现在意识到,您指的是一种更一般的类型,即Seq vs List`Array',如果没有它,使用函数的第一个结构实际上将定义函数必须接受的类型。在这种情况下,使用seq->vs seq->float有什么区别吗?@tls诚然,方差映射的编写方式不应该被认为是惯用的;它最好用-mean x>>fun a->a*a来表达。如果您知道类型,但不希望它是泛型的,您也可以指定它;只是seq->float更容易输入。