- 💬 写在前面:本章我们将探讨 OCaml 中的元组(tuple)和列表(list),它们是函数式编程语言中最常用的数据结构。
-
目录
0x00 元组(Tuple)
0x01 访问元组中的元素
0x02 列表(list)
0x03 列表的 head 和 tail
0x04 基本列表操作符
0x00 元组(Tuple)
元组 (tuple) 是值的 有序集合 (in-order set) ,例如:
元组 (1, "one") 包含了一个整数和一个字符串,其类型可以表示为 int * string。
元组 (2, "two", true) 包含了一个整数, 一个字符串和一个布尔值,其类型为 int * string * bool。
# let x = (1, "one");;
val x : int * string = (1, "one")
# let y = (2, "two", true);;
val y : int * string * bool = (2, "two", true)
0x01 访问元组中的元素
要访问元组的每个元素,可以使用模式匹配。
例如,可以定义如下函数 和 ,用于获取由两个元素组成的元组的第一个和第二个元素。
# let fst p = match p with (x,_) -> x;;
val fst : ’a * ’b -> ’a = <fun>
# let snd p = match p with (_,x) -> x;;
val snd : ’a * ’b -> ’b = <fun>
或者可以直接在函数的参数中使用元组模式,如下所示:
# let fst (x,_) = x;;
val fst : ’a * ’b -> ’a = <fun>
# let snd (_,x) = x;;
val snd : ’a * ’b -> ’b = <fun>
类型 'a * 'b -> 'a
表示函数 fst
接受一个由任意类型 和 组成的元组作为输入,
并返回类型为 的值。通过函数的类型,我们可以推断出函数的大致作用。
不仅在函数参数中,还可以在 let
表达式中使用元组模式。例如,看下面的代码:
# let p = (1, true);;
val p : int * bool = (1, true)
# let (x,y) = p;;
val x : int = 1
val y : bool = true
代表元组 ,并将其分解为 和 。
0x02 列表(list)
列表 (List) 是具有相同类型的元素序列。
例如, 由数字 1, 2, 3 组成的列表表示为 ,其类型是 int list :
# [1; 2; 3];;
- : int list = [1; 2; 3]
在 OCaml 中,列表中的每个元素用分号 ; 分隔。将每个元素用逗号 ,
分隔的列表 被识别为包含元组 作为其元素的列表 ,
因此需要注意这一点:
# [1,2,3];;
- : (int * int * int) list = [(1, 2, 3)]
# [(1,2,3)];;
- : (int * int * int) list = [(1, 2, 3)]
空列表在 OCaml 中用 表示,其类型是 'a list,表示多态类型:
# [];;
- : ’a list = []
在 OCaml 中,列表的所有元素必须是相同的类型。
例如, 在 OCaml 中不是一个列表:
# [1; true];;
Error: This expression has type bool but an expression
was expected of type int
这种限制也源于静态类型系统。
例如,在具有动态类型系统 (如 Python) 的语言中,列表可以包含不同类型的值。
另外,列表是有序元素的序列。因此,下面这两个列表是不同的列表。
# [1;2;3] = [2;3;1];;
- : bool = false
0x03 列表的 head 和 tail
列表的第一个元素称为 head,除第一个元素外剩余的列表称为 tail,即头和尾。
举个例子,对于列表:
头是 ,尾是 。
一般来说,对于类型为 t 的列表,头的类型是 t,尾的类型是 t list 。
列表的元素可以是任意类型的值,当然,每个元素的类型必须相同。
# [1;2;3;4;5];;
- : int list = [1; 2; 3; 4; 5]
# ["OCaml"; "Java"; "C"];;
- : string list = ["OCaml"; "Java"; "C"]
# [(1,"one"); (2,"two"); (3,"three")];;
- : (int * string) list =[(1, "one"); (2, "two"); (3, "three")]
# [[1;2;3];[2;3;4];[4;5;6]];;
- : int list list = [[1; 2; 3]; [2; 3; 4]; [4; 5; 6]]
最后一个例子是整数列表的列表 (int list list) 。
在这种情况下, 每个元素对应的列表可以有不同的长度。
# [[1;2;3]; [4]; []];;
- : int list list = [[1; 2; 3]; [4]; []]
列表 和 ,尽管长度不同,都是整数列表;
空列表 是多态类型,因此也可以是整数列表。
0x04 基本列表操作符
首先是 ::(读作 cons),它可以在列表的最前面添加一个元素,从而创建一个新的列表。
# 1::[2;3];;
- : int list = [1; 2; 3]
# 1::2::3::[];;
- : int list = [1; 2; 3]
将两个列表连接在一起时,使用 @ (读作 append)。
# [1; 2] @ [3; 4; 5];;
- : int list = [1; 2; 3; 4; 5]
在编写处理列表的函数时,模式匹配经常被使用。
例如,我们可以定义函数 和 来获取列表的头部和尾部。
# let hd l =match l with| [] -> raise (Failure "hd is undefined")| a::b -> a;;
val hd : ’a list -> ’a = <fun>
# let tl l =match l with| [] -> raise (Failure "tl is undefined")| a::b -> b;;
val tl : ’a list -> ’a list = <fun>
# hd [1;2;3];;
- : int = 1
# tl [1;2;3];;
- : int list = [2; 3]
列表的头部和尾部对于空列表是未定义的。
如果列表 不是空列表,则可以将其分解为头部 和尾部 ,
其中 返回 , 返回 (如果列表 是单元素列表,则尾部 是空列表)。
请注意,在上述定义中,这两个函数的返回类型是不同的。
如果省略异常处理,也可以简单地定义如下:
# let hd (a::b) = a;;
Warning 8: this pattern-matching is not exhaustive.
Here is an example of a case that is not matched:
[]
val hd : ’a list -> ’a = <fun>
# let tl (a::b) = b;;
Warning 8: this pattern-matching is not exhaustive.
Here is an example of a case that is not matched:
[]
val tl : ’a list -> ’a list = <fun>
在这种情况下,OCaml 提醒我们函数 和 没有考虑空列表的情况。
虽然这些警告信息不是编译错误,但可能在运行时引发未处理的异常,
建议事先处理所有可能的情况。举个例子,我们可以尝试编写一个函数来计算列表的长度:
# let rec length l =match l with[] -> 0|h::t -> 1 + length t;;
val length : ’a list -> int = <fun>
# length [1;2;3];;
- : int = 3
给定的列表 如果是空列表,则定义其长度为 0。
如果不为空,则可以将列表 分为头部 和尾部 ,
此时列表 l 的长度应该等于尾部 的长度加 1。
根据上述定义,由于头部 没有被使用,可以用下划线 (_) 来表示省略:
let rec length l =match l with[] -> 0|_::t -> 1 + length t
📌 [ 笔者 ] 王亦优
📃 [ 更新 ] 2024.6.28
❌ [ 勘误 ] /* 暂无 */
📜 [ 声明 ] 由于作者水平有限,本文有错误和不准确之处在所难免,本人也很想知道这些错误,恳望读者批评指正!
📜 参考资料 - R. Neapolitan, Foundations of Algorithms (5th ed.), Jones & Bartlett, 2015. - T. Cormen《算法导论》(第三版),麻省理工学院出版社,2009年。 - T. Roughgarden, Algorithms Illuminated, Part 1~3, Soundlikeyourself Publishing, 2018. - J. Kleinberg&E. Tardos, Algorithm Design, Addison Wesley, 2005. - R. Sedgewick&K. Wayne,《算法》(第四版),Addison-Wesley,2011 - S. Dasgupta,《算法》,McGraw-Hill教育出版社,2006。 - S. Baase&A. Van Gelder, Computer Algorithms: 设计与分析简介》,Addison Wesley,2000。 - E. Horowitz,《C语言中的数据结构基础》,计算机科学出版社,1993 - S. Skiena, The Algorithm Design Manual (2nd ed.), Springer, 2008. - A. Aho, J. Hopcroft, and J. Ullman, Design and Analysis of Algorithms, Addison-Wesley, 1974. - M. Weiss, Data Structure and Algorithm Analysis in C (2nd ed.), Pearson, 1997. - A. Levitin, Introduction to the Design and Analysis of Algorithms, Addison Wesley, 2003. - A. Aho, J. Hopcroft, and J. Ullman, Data Structures and Algorithms, Addison-Wesley, 1983. - E. Horowitz, S. Sahni and S. Rajasekaran, Computer Algorithms/C++, Computer Science Press, 1997. - R. Sedgewick, Algorithms in C: 第1-4部分(第三版),Addison-Wesley,1998 - R. Sedgewick,《C语言中的算法》。第5部分(第3版),Addison-Wesley,2002 |