模块 Stdlib.ArrayLabels

module ArrayLabels: ArrayLabels

type 'a t = 'a array 

数组类型的别名。

val length : 'a array -> int

返回给定数组的长度(元素数量)。

val get : 'a array -> int -> 'a

get a n 返回数组 a 的第 n 个元素。第一个元素的编号为 0。最后一个元素的编号为 length a - 1。您也可以写 a.(n) 来代替 get a n

val set : 'a array -> int -> 'a -> unit

set a n x 在原地修改数组 a,用 x 替换第 n 个元素。您也可以写 a.(n) <- x 来代替 set a n x

val make : int -> 'a -> 'a array

make n x 返回一个长度为 n 的新数组,用 x 初始化。这个新数组的所有元素最初在物理上都与 x 相等(在 == 谓词的意义上)。因此,如果 x 是可变的,它将在数组的所有元素之间共享,并且通过数组的某个条目修改 x 将同时修改所有其他条目。

val create_float : int -> float array

create_float n 返回一个长度为 n 的新的浮点数组,其数据未初始化。

val init : int -> f:(int -> 'a) -> 'a array

init n ~f 返回一个长度为 n 的新数组,其中第 i 个元素初始化为 f i 的结果。换句话说,init n ~ff 按顺序应用于整数 0n-1 的结果制成表格。

val make_matrix : dimx:int -> dimy:int -> 'a -> 'a array array

make_matrix ~dimx ~dimy e 返回一个二维数组(数组的数组),第一维为 dimx,第二维为 dimy。这个新矩阵的所有元素最初在物理上都与 e 相等。矩阵 m 的元素 (x,y) 使用记号 m.(x).(y) 访问。

val init_matrix : dimx:int -> dimy:int -> f:(int -> int -> 'a) -> 'a array array

init_matrix ~dimx ~dimy ~f 返回一个二维数组(数组的数组),第一维为 dimx,第二维为 dimy,其中索引为 (x,y) 的元素用 f x y 初始化。矩阵 m 的元素 (x,y) 使用记号 m.(x).(y) 访问。

val append : 'a array -> 'a array -> 'a array

append v1 v2 返回一个新的数组,包含数组 v1v2 的串联。

val concat : 'a array list -> 'a array

ArrayLabels.append 相同,但串联了数组列表。

val sub : 'a array -> pos:int -> len:int -> 'a array

sub a ~pos ~len 返回一个新的数组,长度为 len,包含数组 a 的第 pospos + len - 1 个元素。

val copy : 'a array -> 'a array

copy a 返回 a 的副本,也就是说,一个包含与 a 相同元素的新数组。

val fill : 'a array -> pos:int -> len:int -> 'a -> unit

fill a ~pos ~len x 在原地修改数组 a,在第 pospos + len - 1 个元素中存储 x

val blit : src:'a array -> src_pos:int -> dst:'a array -> dst_pos:int -> len:int -> unit

blit ~src ~src_pos ~dst ~dst_pos ~len 将数组 src 中的 len 个元素从第 src_pos 个元素开始复制到数组 dst,从第 dst_pos 个元素开始。即使 srcdst 是同一个数组,并且源和目标块重叠,它也能正常工作。

val to_list : 'a array -> 'a list

to_list a 返回 a 中所有元素的列表。

val of_list : 'a list -> 'a array

of_list l 返回一个新的数组,包含 l 中的元素。

迭代器

val iter : f:('a -> unit) -> 'a array -> unit

iter ~f a 依次将函数 f 应用于 a 中的所有元素。它等效于 f a.(0); f a.(1); ...; f a.(length a - 1); ()

val iteri : f:(int -> 'a -> unit) -> 'a array -> unit

ArrayLabels.iter 相同,但函数应用于元素的索引作为第一个参数,元素本身作为第二个参数。

val map : f:('a -> 'b) -> 'a array -> 'b array

map ~f a 将函数 f 应用于 a 中的所有元素,并构建一个包含 f 返回结果的数组:[| f a.(0); f a.(1); ...; f a.(length a - 1) |]

val map_inplace : f:('a -> 'a) -> 'a array -> unit

map_inplace ~f a 将函数 f 应用于 a 中的所有元素,并在原地更新它们的值。

val mapi : f:(int -> 'a -> 'b) -> 'a array -> 'b array

ArrayLabels.map 相同,但函数应用于元素的索引作为第一个参数,元素本身作为第二个参数。

val mapi_inplace : f:(int -> 'a -> 'a) -> 'a array -> unit

ArrayLabels.map_inplace 相同,但函数应用于元素的索引作为第一个参数,元素本身作为第二个参数。

val fold_left : f:('acc -> 'a -> 'acc) -> init:'acc -> 'a array -> 'acc

fold_left ~f ~init a 计算 f (... (f (f init a.(0)) a.(1)) ...) a.(n-1),其中 n 是数组 a 的长度。

val fold_left_map : f:('acc -> 'a -> 'acc * 'b) -> init:'acc -> 'a array -> 'acc * 'b array

fold_left_mapArrayLabels.fold_leftArrayLabels.map 的组合,它通过对 f 的调用将一个累加器穿插起来。

val fold_right : f:('a -> 'acc -> 'acc) -> 'a array -> init:'acc -> 'acc

fold_right ~f a ~init 计算 f a.(0) (f a.(1) ( ... (f a.(n-1) init) ...)),其中 n 是数组 a 的长度。

两个数组上的迭代器

val iter2 : f:('a -> 'b -> unit) -> 'a array -> 'b array -> unit

iter2 ~f a b 将函数 f 应用于 ab 中的所有元素。

val map2 : f:('a -> 'b -> 'c) -> 'a array -> 'b array -> 'c array

map2 ~f a b 将函数 f 应用于 ab 中的所有元素,并构建一个包含 f 返回结果的数组:[| f a.(0) b.(0); ...; f a.(length a - 1) b.(length b - 1)|]

数组扫描

val for_all : f:('a -> bool) -> 'a array -> bool

for_all ~f [|a1; ...; an|] 检查数组的所有元素是否满足谓词 f。也就是说,它返回 (f a1) && (f a2) && ... && (f an)

val exists : f:('a -> bool) -> 'a array -> bool

exists ~f [|a1; ...; an|] 检查数组中是否至少有一个元素满足谓词 f。也就是说,它返回 (f a1) || (f a2) || ... || (f an)

val for_all2 : f:('a -> 'b -> bool) -> 'a array -> 'b array -> bool

ArrayLabels.for_all 相同,但适用于具有两个参数的谓词。

val exists2 : f:('a -> 'b -> bool) -> 'a array -> 'b array -> bool

ArrayLabels.exists 相同,但适用于具有两个参数的谓词。

val mem : 'a -> set:'a array -> bool

mem a ~set 为真,当且仅当 a 在结构上等于 set 中的一个元素(即,在 set 中存在一个 x,使得 compare a x = 0)。

val memq : 'a -> set:'a array -> bool

ArrayLabels.mem 相同,但使用物理等式而不是结构等式来比较数组元素。

val find_opt : f:('a -> bool) -> 'a array -> 'a option

find_opt ~f a 返回数组 a 中第一个满足谓词 f 的元素,或者如果数组 a 中没有满足 f 的值,则返回 None

val find_index : f:('a -> bool) -> 'a array -> int option

find_index ~f a 返回 Some i,其中 i 是数组 a 中第一个满足 f x 的元素的索引,如果存在这样的元素。

如果不存在这样的元素,则返回 None

val find_map : f:('a -> 'b option) -> 'a array -> 'b option

find_map ~f a 按顺序将 f 应用于 a 中的元素,并返回第一个 Some v 形式的结果,或者如果不存在,则返回 None

val find_mapi : f:(int -> 'a -> 'b option) -> 'a array -> 'b option

find_map 相同,但谓词应用于元素的索引作为第一个参数(从 0 开始计数),元素本身作为第二个参数。

数组对

val split : ('a * 'b) array -> 'a array * 'b array

split [|(a1,b1); ...; (an,bn)|]([|a1; ...; an|], [|b1; ...; bn|])

val combine : 'a array -> 'b array -> ('a * 'b) array

combine [|a1; ...; an|] [|b1; ...; bn|][|(a1,b1); ...; (an,bn)|]。如果两个数组的长度不同,则会引发 Invalid_argument 错误。

排序和洗牌

val sort : cmp:('a -> 'a -> int) -> 'a array -> unit

根据比较函数对数组进行升序排序。比较函数必须在两个参数相等时返回 0,如果第一个参数更大则返回正整数,如果第一个参数更小则返回负整数(有关完整规范,请参见下文)。例如,compare 是一个合适的比较函数。调用 sort 后,数组将被就地排序为升序。保证 sort 运行在常数堆空间和(最多)对数栈空间中。

当前实现使用堆排序。它运行在常数栈空间中。

比较函数规范:设 a 为数组,cmp 为比较函数。以下内容对于 a 中的所有 xyz 必须为真。

  • cmp x y > 0 当且仅当 cmp y x < 0
  • 如果 cmp x y >= 0 且 cmp y z >= 0,则 cmp x z >= 0

sort 返回时,a 包含与之前相同的元素,但以如下方式重新排序,对于所有 a 的有效索引 i 和 j

  • cmp a.(i) a.(j) >= 0 当且仅当 i >= j
val stable_sort : cmp:('a -> 'a -> int) -> 'a array -> unit

ArrayLabels.sort 相同,但排序算法是稳定的(即比较相等的元素保持其原始顺序),并且不保证运行在常数堆空间中。

当前实现使用归并排序。它使用长度为 n/2 的临时数组,其中 n 是数组的长度。它通常比 ArrayLabels.sort 的当前实现更快。

val fast_sort : cmp:('a -> 'a -> int) -> 'a array -> unit

ArrayLabels.sortArrayLabels.stable_sort 相同,无论哪一个在典型输入上更快。

val shuffle : rand:(int -> int) -> 'a array -> unit

shuffle ~rand a 使用 rand 的随机性对 a 的元素进行随机排列。排列的分布是均匀的。

rand 必须是,调用 rand n 返回范围 [0;n-1] 中的均匀分布随机数。可以使用 Random.int 来实现这一点(不要忘记 初始化 生成器)。

数组和序列

val to_seq : 'a array -> 'a Seq.t

以升序对数组进行迭代。迭代期间对数组的修改将反映在序列中。

val to_seqi : 'a array -> (int * 'a) Seq.t

以升序对数组进行迭代,生成索引和元素。迭代期间对数组的修改将反映在序列中。

val of_seq : 'a Seq.t -> 'a array

从生成器创建数组。

数组和并发安全性

在从多个域并发访问数组时,必须小心:访问数组永远不会导致程序崩溃,但不同步的访问可能会产生令人惊讶的(非顺序一致的)结果。

原子性

访问多个数组元素的每个数组操作都不是原子的。这包括迭代、扫描、排序、拆分和合并数组。

例如,考虑以下程序

let size = 100_000_000
let a = ArrayLabels.make size 1
let d1 = Domain.spawn (fun () ->
   ArrayLabels.iteri ~f:(fun i x -> a.(i) <- x + 1) a
)
let d2 = Domain.spawn (fun () ->
  ArrayLabels.iteri ~f:(fun i x -> a.(i) <- 2 * x + 1) a
)
let () = Domain.join d1; Domain.join d2

执行完此代码后,数组 a 的每个字段的值要么是 2345。如果需要原子性,则用户必须实现自己的同步(例如,使用 Mutex.t)。

数据竞争

如果两个域仅访问数组的不同部分,则观察到的行为等效于来自两个域的操作的某些顺序交错。

当两个域访问同一个数组元素而没有同步,并且至少其中一个访问是写入操作时,就会发生数据竞争。在没有数据竞争的情况下,观察到的行为等效于来自不同域的操作的某些顺序交错。

应尽可能避免数据竞争,使用同步来协调对数组元素的访问。

实际上,在存在数据竞争的情况下,程序不会崩溃,但观察到的行为可能不等效于来自不同域的操作的任何顺序交错。然而,即使在存在数据竞争的情况下,读取操作也会返回先前对该位置的某个写入操作的值(对于浮点数组,有一些例外)。

浮点数组

在存在数据竞争的情况下,浮点数组有另外两个注意事项。

首先,blit 操作可能会逐字节复制数组。此类 blit 操作与其他操作之间的数据竞争可能会产生令人惊讶的值,因为撕裂:部分写入与其他操作交错会导致浮点值,而这些值在顺序执行时不会存在。

例如,在

let zeros = Array.make size 0.
let max_floats = Array.make size Float.max_float
let res = Array.copy zeros
let d1 = Domain.spawn (fun () -> Array.blit zeros 0 res 0 size)
let d2 = Domain.spawn (fun () -> Array.blit max_floats 0 res 0 size)
let () = Domain.join d1; Domain.join d2

结束时,res 数组可能包含既不是 0. 也不是 max_float 的值。

其次,在 32 位体系结构上,获取或设置字段涉及两个独立的内存访问。在存在数据竞争的情况下,用户可能会观察到对任何操作的撕裂。