F#嵌套列表问题

F# nested list problems

我有一个列表,例如

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)

谢谢。

您可以这样做:

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 函数使用 Seq.length.

计算课程数

由于可能有重复条目,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

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

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

[(_,[])]

单个元素是一个元组:

(_,[])

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

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

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

警告是

Incomplete pattern matches on this expression. For example, the value '([;],_)' may indicate a case not covered by the pattern(s).

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

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 的表达式的一部分。 "Divide and conquer" 将使您更容易推理您的代码。

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

最后,您可能想阅读有关辅助函数以及如何编写尾递归函数的信息,尽管您可能仍处于课程中不应使用它们的阶段。一个重要的概念是 "accumulator."