返回List的平均值,如果存在这样的列表

时间:2016-10-12 15:10:37

标签: .net f# functional-programming

我想这样做,如果作为参数传递的列表是一个浮点列表,而不是其他任何东西,那么返回浮点数List的平均值(作为一个浮点数)整数列表。它应该只计算浮点数列表的平均值。

所以说我传递一个int列表,然后它应该返回none语句。或者如果我传递一个空列表,它也应该返回none语句。

let avg x = 
    let average = List.averageBy (fun elem -> float elem) x

    match List.tryFind x with
    |Some(x) -> printfn "The average of the list is %f" average
    |None -> "There are no float elements in the list, hence no average"

avg [1.0;5.0;6.0;10.0]

得到错误FS0001:这个表达式应该有''a-> bool'类型,但这里有''b list'类型。

哪个有道理。但是,我无法弄清楚在哪里给它一个bool值。

修改

我正在考虑使用List.forall检查列表是否包含浮点数。仍然遇到同样的BOOL问题。

2 个答案:

答案 0 :(得分:4)

回答的捷径

以下假设您传递了不同类型的列表,我假设您是因为您说您必须使用List模块中的函数。

let average list =
    let isFloat (x : obj) =
        match x with
        | :? float -> true
        | _        -> false
    let toFloat (x : obj) = x :?> float
    if List.forall isFloat list then
        List.averageBy toFloat list
        |> Some
    else None

说明

您收到编译错误的原因是因为List.tryFind使用不正确,但这不是完整的故事。正确使用tryFind看起来像

let findFirstEven list = List.tryFind (fun x -> x % 2 = 0) list

F#是强类型的,这意味着一般,我们希望您不要尝试将多种类型传递给函数。函数期望每个参数只有一种类型,传递不同的东西是编译时错误。

也就是说,因为F#是基于.NET构建的并且支持类和继承,所以函数可以接受一个类,但是如果实际传递的对象是预期的一个派生类,它可以做不同的事情。 。在F#中,类型强制测试是这样完成的:

type Person (name) =
    member this.Name : string = name

type Student (name, university) =
    inherit Person (name)
    member this.University : string = university

type Adult (name, job) =
    inherit Person (name)
    member this.Job : string = job

let greet (person : Person) =
    match person with
    | :? Student as student -> printfn "Hello, %s of %s" student.Name student.University
    | :? Adult   as adult   -> printfn "Hello, %s.  I hope you enjoy working at %s!" adult.Name adult.Job
    | _                     -> printfn "Nice to meet you, %s" person.Name

这仅适用于派生类型,而不适用于真正的F#泛型类型,例如尝试将'T listfloat list或其他任何内容匹配。但是,.NET的结构方式意味着每种可能的类型都是obj,或者来自obj。这意味着如果我们允许传递任何可能的类型,我们可以测试它是否为float list

let average (value : obj) =
    match value with
    | :? List<float> as list -> List.average list |> Some
    | _ -> None

这个函数可以用字面上的任何可表达的类型,所以我们真的真正试图摆脱F#中的写作。相反,我们可以保证您可以保证您将被传递一系列事物,但事物可能是彼此不同的类型。同样,这是我们在编写F#时必须避免的情况,但是现在:

let average list =
    let isFloat (x : obj) =
        match x with
        | :? float -> true
        | _        -> false
    let toFloat (x : obj) = x :?> float
    if List.forall isFloat list then
        List.averageBy toFloat list
        |> Some
    else None

这会单独测试列表中的每个元素是否为float,然后如果是,则将列表元素强制转换为float并对其进行平均。需要向下:?>,因为否则List.average不知道如何执行平均值(它仍然认为列表可能是任何内容),但它也是一个危险的运算符使用不慎。如果对派生类型的强制转换失败,则代码将抛出异常,这可能会终止您的程序。在这里,我们知道它是安全的,因为我们只测试了相同的演员阵容,但您应该避免在代码中使用它,除非您确定它是安全的。即使这样,这对于惯用的F#代码也没有用,因为我们主要希望你使用真正的泛型来正确使用类型系统。

答案 1 :(得分:2)

这是应该有用的东西:

let avg (arg : obj) =
  match arg with
    :? list<float> as xs -> Some (List.average xs)
  | _                    -> None

或(同样的,另一种写作方式):

let avg : obj -> _ = function
  :? list<float> as xs -> Some (List.average xs)
| _                    -> None