作为一个简单挑战的一部分,我要从头开始编写几个简单的统计函数,并且尝试以最“惯用的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
函数,但是我觉得可能有一些更美,更惯用的方式来定义前两个。
有什么建议吗?
答案 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
没有注释,由于值限制,mean
和stdDev
都不会在这里被编译而不在模块中被调用。即使这样,它们也仅限于遇到的实现System.Collections.Generic.IEnumerable<'T>
的第一种类型。
另一方面,variance
的定义不适合eta简化,也没有这些问题。组合用于组合两个函数:从partially applied的平均值中减去,然后将一个值自身相乘。