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

时间:2018-10-15 15:14:20

标签: f# idiomatic

作为一个简单挑战的一部分,我要从头开始编写几个简单的统计函数,并且尝试以最“惯用的F#”方式编写它们。我对函数式编程非常陌生,因此我希望从一开始就学习如何创建简单的东西。

这是我到目前为止所拥有的:

a,b,id    
x,y,1    
x,y,2    
x,z,3    
t,y,4    
t,y,5

我喜欢如何使用合成来定义count,id 1,1 2,2 1,3 1,4 2,5 函数,但是我觉得可能有一些更美,更惯用的方式来定义前两个。

有什么建议吗?

3 个答案:

答案 0 :(得分:2)

您的代码完全正确且惯用。

就个人而言,我尽可能选择一种衬板。这样,我可以对齐代码以突出功能之间的异同。模式就是那样跳向你的。

let mean     x = (Seq.sum x) / (float (Seq.length x))
let variance x = x |> Seq.map (fun a -> pown (a - (mean x)) 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#中,最好避免使用>>合成运算符,而应使用管道|>。 像这样的组合函数有很多问题。例如,上面的代码将不可能(使用列表和数组等不同类型)。

答案 1 :(得分:1)

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

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

正如AMieres在另一封回复中所述,您还可以考虑使用与列表不同的类型。 List很不错而且很实用,但是Seq可以使代码适用于任何集合。另外,如果您要使用更大的数据进行计算,Array可能会更快一些。

答案 2 :(得分:0)

函数组合运算符并不像它的声誉那样糟糕,它只需要一点注意就不会碰到value restriction或相关问题之一。同义错误FS0030表示:

  

或者使“ stdDev”的参数明确,或者,如果不这样做,   为了使其通用,请添加类型注释。

我们还能够添加类型注释,以使让界值比其他方式更通用。

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

没有注释,由于值限制,meanstdDev都不会在这里被编译而不在模块中被调用。即使这样,它们也仅限于遇到的实现System.Collections.Generic.IEnumerable<'T>的第一种类型。

另一方面,variance的定义不适合eta简化,也没有这些问题。组合用于组合两个函数:从partially applied的平均值中减去,然后将一个值自身相乘。