【问题标题】:F# nested list problemsF# 嵌套列表问题
【发布时间】:2015-03-20 06:23:03
【问题描述】:

我有一个列表,例如

let stdCourseList = [(10,[100;101;102]); (11,[101]); (14,[]); (12,[100;101])];;

这是一个包含学生 ID 和课程 ID 的列表

我想实现一个能够计算学生所学课程数量的函数。 比如我这样做

numbOfCourse(stdCourseList,10) 

我会得到 3,因为 id 为 10 的学生参加了 3 门课程

到目前为止,我有这个代码:

let rec numbOfCourse = function
|(([], id)) -> 0
|(((id2:int,(y:int)::ys)::xs),id) ->
      if id=id2 then 1 //should a code to count the list of the course
      else numbOfCourse(xs,id);;

但是当我运行它时,我得到了这个错误

   let rec numbOfCourse = function
  ------------------------^^^^^^^^

  stdin(1471,25): warning FS0025: Incomplete pattern matches on this
  expression. For example, the value '([(_,[])],_)' may indicate a case not 
  covered by the pattern(s).

我真的不明白为什么会出现此错误以及错误的含义。有人可以帮我解释一下吗?处理嵌套列表的最佳方法是什么?

这部分错了吗?

(((id2:int,(y:int)::ys)::xs),id)

谢谢。

【问题讨论】:

    标签: list f# nested


    【解决方案1】:

    错误消息表明您的模式并不详尽。示例 ([(_,[])],_) 表明您的函数不处理课程列表由单个元素组成并且课程列表中的单个元素具有空列表作为其第二个成员的情况。

    整个模式是一个元组。元组模式的第二部分将匹配任何值;这就是下划线的意思。元组的第一部分是具有单个元素的列表:

    [(_,[])]
    

    单个元素是一个元组:

    (_,[])
    

    该元组的第一部分将匹配任何值;第二部分匹配空列表。

    如果你知道你永远不会得到这样的课程列表,你可以忽略警告,但我发现当编译器发现不完整的模式匹配时添加一个失败案例会更好,因为这样编译器会通知我我未能涵盖的其他案例。例如,如果您将这一行添加到您的函数中,您将收到另一个警告:

    | ([(_,[])],_) -> failwith "unexpected pattern"
    

    警告是

    此表达式的模式匹配不完整。例如,值 '([;],_)' 可能表示模式未涵盖的情况。

    一般来说,我发现尝试编写复杂的模式来分解复杂的结构,如列表的元组列表是危险的。最好一次处理一层:

    let rec f = function
                | [], _ -> //your base case here
                | (studentId, courseList) :: tail, idParameter when studentId = idParameter -> //one recursive case here
                | _ :: tail, id -> // another recursive case here
    

    如果您需要分解课程 ID 列表,您可以使用单独的 match 表达式来执行此操作,作为替换 one recursive case here 的表达式的一部分。 “分而治之”将更容易推理您的代码。

    不过,如果由于您的课程要求而不能选择此选项,那么将这种结构分解到一个列表中应该是易于处理的。只需将每个列表视为具有两种可能的状态:空和非空。这将减少排列的数量。

    最后,您可能想了解帮助函数以及如何编写尾递归函数,尽管您可能仍处于课程中不应该使用它们的阶段。一个重要的概念是“累加器”。

    【讨论】:

    • 哇,非常感谢先生,这就是我一直在寻找的答案。我刚刚发现我可以做到这一点idParameter when studentId = idParameter,我还需要一个递归案例,即_::tail,id -> f(tail,id)。你真棒
    【解决方案2】:

    你可以这样做:

    let getCourseCount idx l =
        l
        |> Seq.filter (fun (i, _) -> i = idx)
        |> Seq.map (fun (_, courses) -> courses |> Seq.length)
        |> Seq.sum
    

    getCourseCount 函数具有签名'a -> seq<'a * #seq<'c>> -> int when 'a : equality,因此它非常通用。它接受任何序列作为输入,只要每个元素是一个元组,第二个元素是另一个序列。

    stdCourseList 值适合输入类型,因为它是 (int * int list) list,而 listseq

    filter 函数仅选择第一个元素等于输入 idx 的元组。

    可能有多个元素通过过滤器(例如,如果 10 有两个条目),但对于每个条目,map 函数使用 @ 计算课程数987654331@.

    由于可能有重复的条目,Seq.sum 可用于将这些数字相加。

    FSI 示例会话:

    > stdCourseList |> getCourseCount 10;;
    val it : int = 3
    > stdCourseList |> getCourseCount 11;;
    val it : int = 1
    > stdCourseList |> getCourseCount 14;;
    val it : int = 0
    > stdCourseList |> getCourseCount 12;;
    val it : int = 2
    

    【讨论】:

    • 感谢您的出色回答先生。但是我老师给出的问题特别要求一个看起来像getCourseCount(stdCourseList,id) : (int * int list) list * int -> int的函数。
    • @user142068 您可以使用List 模块执行完全相同的操作,但即便如此,签名也会比您列出的更通用。您可以手动将参数限制为所需的任何签名。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多