dragonfly02
dragonfly02

Reputation: 3679

F# generic function to calculate mean

How to write a generic F# function to calculate the mean of a collection? I tried:

let inline mean x = 
    let length = x |> Seq.length
    let sum = x |> Seq.sum
    LanguagePrimitives.DivideByInt sum length

This seems working fine for float and decimals but surprisingly doesn't work for int with an error saying The type 'int' does not support the operator 'DivideByInt'.

Upvotes: 5

Views: 689

Answers (3)

AMieres
AMieres

Reputation: 5004

This is one way to create a generic mean function.

First create a more generic divideByInt:

type Helper = Helper of int with
    static member (&/)(Helper b, a) = a /          b 
    static member (&/)(Helper b, a) = a / (float   b)
    static member (&/)(Helper b, a) = a / (decimal b)

let inline divideByInt a b = Helper b &/ a

This version of divideByInt works with float, decimal and int.

Then use it just like in your original solution:

let inline mean x = 
    let length = x |> Seq.length
    let sum    = x |> Seq.sum
    divideByInt sum length

and test it like this:

mean [5. ; 3.] |> printfn "%A"   // 4.0
mean [5  ; 3 ] |> printfn "%A"   // 4

Upvotes: 2

user9723177
user9723177

Reputation:

Note that summing the sequence then dividing by length is vulnerable to overflow errors. It would be better to use an incremental algorithm, e.g. implement one of the algorithms from this question on math.stackexchange.com.

For example:

let inline mean xs =
    xs |> Seq.map float
       |> Seq.fold (fun (mean, n) x -> mean + (x-mean) / (float n), n+1) (0.0, 1)
       |> fst

Upvotes: 2

rmunn
rmunn

Reputation: 36708

To begin with, this function already exists in F#, and is called Seq.average. If you're doing this just to have that function, you can stop reading now. If you're doing this as a personal learning exercise, read on. :-)

The F# documentation on DivideByInt has the answer here:

If a type supports DivideByInt, the type supports exact division (floating-point division), rather than integer division, which rounds down to the nearest integer result.

Functions like Seq.average work only if the element type supports exact division. If you try to use Seq.average with an integer sequence, you get an error that indicates that the element type must implement DivideByInt. Typically, you can resolve this error by using Seq.averageBy and adding a cast to a floating-point value.

So to get your mean function to work on integers, you'd need to write it as follows:

let intMean (x : int seq) = 
    let length = x |> Seq.length
    let sum = x |> Seq.map float |> Seq.sum
    LanguagePrimitives.DivideByInt sum length

Upvotes: 2

Related Questions