数组

简介

在 OCaml 中,数组是一组相同类型元素的集合。与列表不同,数组可以通过用相同类型其他值替换元素来进行修改,但不能调整大小。数组还允许高效地访问任何位置的元素。

尽管存在这些差异,但许多可用于数组的函数与可用于列表的函数类似。有关这些函数的更多详细信息,请参考 列表教程文档

本教程旨在介绍 OCaml 中数组的概念,并展示最有用的函数和用例。

数组通常在 OCaml 中用于以下任务:

  • 存储和处理大量数据
  • 实现需要随机访问和修改元素的算法
  • 处理矩阵和其他多维数据结构

创建数组

要在 OCaml 中创建数组,可以使用 [| ...; ... |] 语法,该语法允许您直接指定每个元素的值。例如,要创建包含值 1、2、3、4 和 5 的数组,您将编写 [| 1; 2; 3; 4; 5 |]

# [| 1; 2; 3; 4; 5 |];;
- : int array = [|1; 2; 3; 4; 5|]

或者,您可以使用 Array.make 函数创建数组,该函数接受两个参数:数组的长度和每个元素的初始值。例如,要创建长度为 5 且所有元素都初始化为 0 的数组,您可以编写

# let zeroes = Array.make 5 0;;
val zeroes : int array = [|0; 0; 0; 0; 0|]

Array.init 通过将函数应用于数组的每个索引(从 0 开始)来生成给定长度的数组。以下代码行使用将参数加倍的函数创建包含前 5 个偶数的数组

# let even_numbers = Array.init 5 (fun i -> i * 2);;
val even_numbers : int array = [|0; 2; 4; 6; 8|]

访问数组元素

您可以使用 .(index) 语法访问数组的单个元素,其中 index 是要访问的元素的索引。第一个元素的索引为 0,最后一个元素的索引为数组大小减 1。例如,要访问数组 even_numbers 的第三个元素,您将编写

# even_numbers.(2);;
- : int = 4

修改数组元素

要修改数组中的元素,我们只需使用索引运算符为其分配一个新值。例如,要将上面创建的数组 even_numbers 的第三个元素的值更改为 42,我们必须编写

# even_numbers.(2) <- 42;;
- : unit = ()

请注意,此操作返回 unit,而不是修改后的数组。even_numbers 会作为副作用就地修改。

标准库 Array 模块

OCaml 提供了许多用于处理数组的有用函数。以下是一些最常见的函数:

数组的长度

Array.length 函数返回数组的大小

# Array.length even_numbers;;
- : int = 5

遍历数组

Array.iter 将函数一次应用于数组的每个元素。给定函数必须返回 unit,通过副作用进行操作。要打印上面创建的数组 zeroes 的所有元素,我们可以将 print_int 应用于每个元素

# Array.iter (fun x -> print_int x; print_string " ") zeroes;;
0 0 0 0 0 - : unit = ()

也可以使用 for 循环遍历数组。以下是用循环实现的相同示例

# for i = 0 to Array.length zeroes - 1 do
    print_int zeroes.(i);
    print_string " "
  done;;
0 0 0 0 0 - : unit = ()

映射数组

Array.map 函数通过将给定函数应用于数组的每个元素来创建一个新数组。例如,我们可以获得一个包含 even_numbers 数组中每个数字平方的数组

# Array.map (fun x -> x * x) even_numbers;;
- : int array = [|0; 4; 1764; 36; 64|]

折叠数组

要将数组的所有元素组合成一个结果,我们可以使用 Array.fold_leftArray.fold_right 函数。这些函数接受一个二元函数、一个初始累加器值和一个数组作为参数。二元函数接受两个参数:累加器的当前值和数组的当前元素,然后返回一个新的累加器值。这两个函数都遍历数组,但方向相反。这与 List.fold_leftList.fold_right 本质上相同。

以下是 Array.fold_left 的签名

# Array.fold_left;;
val fold_left : ('a -> 'b -> 'a) -> 'a -> 'b array -> 'a = <fun>

fold_left f init a 计算 f (... (f(f init a.(0)) a.(1)) ...) a.(n-1)

类似地,我们可以使用 Array.fold_right 函数,它交换了参数的顺序

# Array.fold_right;;
val fold_right : ('b -> 'a -> 'a) -> 'b array -> 'a -> 'a = <fun>

fold_right f a init 计算 f a.(0) (f a.(1) ( ... (f a.(n-1) init) ...))

这些函数从整个数组中推导出一个值。例如,它们可用于查找数组的最大元素

# Array.fold_left Int.max min_int even_numbers;;
- : int = 42

排序数组

要排序数组,我们可以使用 Array.sort 函数。此函数接受以下参数:

  • 一个比较函数
  • 一个数组 它根据提供的比较函数对提供的数组进行就地排序,并按升序排序。Array.sort 执行的排序会修改提供的数组的内容,这就是它返回 unit 的原因。例如,要对上面创建的数组 even_numbers 进行排序,我们可以使用
# Array.sort compare even_numbers;;
- : unit = ()
# even_numbers;;
- : int array = [|0; 2; 6; 8; 42|]

将数组的一部分复制到另一个数组中

Array.blit 函数可以高效地将数组的连续部分复制到另一个数组中。类似于 array.(x) <- y 操作,该函数会就地修改目标数组并返回 unit,而不是修改后的数组。假设您想将 ones 的一部分复制到 zeroes

# let ones = Array.make 5 1;;
val ones : int array = [|1; 1; 1; 1; 1|]
# Array.blit ones 0 zeroes 1 2;;
- : unit = ()
# zeroes;;
- : int array = [|0; 1; 1; 0; 0|]

这将从索引 0 开始复制 ones 的两个元素(此数组切片为 [| 1; 1 |])到 zeroes 中,从索引 1 开始。您需要确保提供的两个索引在各自的数组中有效,并且要复制的元素数量在每个数组的范围内。

我们也可以使用此函数将数组的一部分复制到自身

# Array.blit zeroes 1 zeroes 3 2;;
- : unit = ()
# zeroes;;
- : int array = [|0; 1; 1; 1; 1|]

这将从索引 1 开始复制 zeroes 的两个元素到 zeroes 的最后部分,从索引 3 开始。

结论

在本教程中,我们介绍了 OCaml 中数组的基础知识,包括如何创建和操作它们,以及一些最实用的函数和用例。请参考 标准库文档 以浏览 Array 模块的所有函数列表。

帮助改进我们的文档

所有 OCaml 文档都是开源的。发现错误或不清楚的地方?提交一个拉取请求。

OCaml

创新、社区、安全。