module Hashtbl:sig
..end
哈希表和哈希函数。
哈希表是散列关联表,支持就地修改。由于大多数哈希表操作都会修改其输入,因此它们更常用于命令式代码中。查找与键关联的值(参见 Hashtbl.find
、Hashtbl.find_opt
)通常非常快,通常比 Map
中的等效查找更快。
当性能或灵活性至关重要时,可以使用函子 Hashtbl.Make
和 Hashtbl.MakeSeeded
。用户为键类型提供自定义的相等性和哈希函数,并获得针对此特定键类型的自定义哈希表类型。
警告 哈希表的好坏取决于哈希函数。糟糕的哈希函数会将表变成退化的关联列表,查找时间为线性时间而不是常数时间。
多态 Hashtbl.t
哈希表在简单的情况下或交互式环境中很有用。它使用 OCaml 运行时中定义的多态 Hashtbl.hash
函数(在撰写本文时,它是 SipHash),以及多态相等性 (=)
。
参见 示例部分。
未同步访问
对哈希表的未同步访问可能导致哈希表状态无效。因此,必须同步对哈希表的并发访问(例如使用 Mutex.t
)。
type (!'a, !'b)
t
从类型 'a
到类型 'b
的哈希表的类型。
val create : ?random:bool -> int -> ('a, 'b) t
Hashtbl.create n
创建一个新的空哈希表,初始大小为 n
。为了获得最佳效果,n
应与表中预期元素的数量大致相同。表会根据需要增长,因此 n
只是一个初始估计。
可选的 ~random
参数(一个布尔值)控制在每次执行 Hashtbl.create
时是否随机化哈希表的内部组织,或者在所有执行中是否确定性地保持不变。
使用 ~random
设置为 false
创建的哈希表使用固定的哈希函数 (Hashtbl.hash
) 将键分配到桶中。因此,键之间的冲突以确定性方式发生。在面向 Web 的应用程序或其他安全敏感的应用程序中,恶意用户可以利用确定性的冲突模式创建拒绝服务攻击:攻击者发送精心设计的输入以在表中创建许多冲突,从而降低应用程序的速度。
使用 ~random
设置为 true
创建的哈希表使用带种子哈希函数 Hashtbl.seeded_hash
,该函数的种子在哈希表创建时随机选择。实际上,使用的哈希函数是在 2^{30}
个不同的哈希函数中随机选择的。所有这些哈希函数都有不同的冲突模式,从而使上述拒绝服务攻击无效。但是,由于随机化,使用 Hashtbl.fold
或 Hashtbl.iter
枚举哈希表的所有元素不再是确定性的:元素在程序的不同运行中以不同的顺序枚举。
如果未提供 ~random
参数,则默认情况下会在非随机模式下创建哈希表。可以通过调用 Hashtbl.randomize
或在 OCAMLRUNPARAM
环境变量中设置 R
标志以编程方式更改此默认值。
~random
参数不存在,并且所有哈希表都在非随机化模式下创建。val clear : ('a, 'b) t -> unit
清空哈希表。使用 reset
而不是 clear
将桶表的尺寸缩小到其初始尺寸。
val reset : ('a, 'b) t -> unit
清空哈希表并将桶表的尺寸缩小到其初始尺寸。
val copy : ('a, 'b) t -> ('a, 'b) t
返回给定哈希表的副本。
val add : ('a, 'b) t -> 'a -> 'b -> unit
Hashtbl.add tbl key data
在表 tbl
中添加 key
到 data
的绑定。
警告:不会删除 key
的先前绑定,而只是将其隐藏。也就是说,在执行 Hashtbl.remove
tbl key
后,将恢复 key
的先前绑定(如果存在)。(与关联列表的行为相同。)
如果您希望替换元素的经典行为,请参见 Hashtbl.replace
。
val find : ('a, 'b) t -> 'a -> 'b
Hashtbl.find tbl x
返回 tbl
中 x
的当前绑定,如果不存在此类绑定,则引发 Not_found
。
val find_opt : ('a, 'b) t -> 'a -> 'b option
Hashtbl.find_opt tbl x
返回 tbl
中 x
的当前绑定,如果不存在此类绑定,则返回 None
。
val find_all : ('a, 'b) t -> 'a -> 'b list
Hashtbl.find_all tbl x
返回 tbl
中与 x
关联的所有数据的列表。当前绑定首先返回,然后是先前的绑定,按在表中引入的逆序排列。
val mem : ('a, 'b) t -> 'a -> bool
Hashtbl.mem tbl x
检查 x
是否在 tbl
中绑定。
val remove : ('a, 'b) t -> 'a -> unit
Hashtbl.remove tbl x
删除 tbl
中 x
的当前绑定,如果存在则恢复先前的绑定。如果 x
未在 tbl
中绑定,则它不执行任何操作。
val replace : ('a, 'b) t -> 'a -> 'b -> unit
Hashtbl.replace tbl key data
将 tbl
中 key
的当前绑定替换为 key
到 data
的绑定。如果 key
未在 tbl
中绑定,则将 key
到 data
的绑定添加到 tbl
。这在功能上等效于 Hashtbl.remove
tbl key
后跟 Hashtbl.add
tbl key data
。
val iter : ('a -> 'b -> unit) -> ('a, 'b) t -> unit
Hashtbl.iter f tbl
将 f
应用于表 tbl
中的所有绑定。 f
将键作为第一个参数接收,并将关联的值作为第二个参数接收。每个绑定都精确地向 f
提供一次。
将绑定传递给 f
的顺序未指定。但是,如果表中包含多个针对同一键的绑定,则它们将按引入的逆序传递给 f
,即最新的绑定首先传递。
如果哈希表是在非随机化模式下创建的,则绑定枚举的顺序在程序的连续运行之间甚至在 OCaml 的次要版本之间都是可复制的。对于随机化的哈希表,枚举顺序完全是随机的。
如果在迭代期间 f
修改了哈希表,则行为未指定。
val filter_map_inplace : ('a -> 'b -> 'b option) -> ('a, 'b) t -> unit
Hashtbl.filter_map_inplace f tbl
将 f
应用于表 tbl
中的所有绑定,并根据 f
的结果更新每个绑定。如果 f
返回 None
,则丢弃绑定。如果它返回 Some new_val
,则绑定将更新为将键与 new_val
关联。
Hashtbl.iter
的其他注释也适用。
val fold : ('a -> 'b -> 'acc -> 'acc) -> ('a, 'b) t -> 'acc -> 'acc
Hashtbl.fold f tbl init
计算 (f kN dN ... (f k1 d1 init)...)
,其中 k1 ... kN
是 tbl
中所有绑定的键,而 d1 ... dN
是关联的值。每个绑定都精确地向 f
提供一次。
将绑定传递给 f
的顺序未指定。但是,如果表中包含多个针对同一键的绑定,则它们将按引入的逆序传递给 f
,即最新的绑定首先传递。
如果哈希表是在非随机化模式下创建的,则绑定枚举的顺序在程序的连续运行之间甚至在 OCaml 的次要版本之间都是可复制的。对于随机化的哈希表,枚举顺序完全是随机的。
如果在迭代期间 f
修改了哈希表,则行为未指定。
val length : ('a, 'b) t -> int
Hashtbl.length tbl
返回 tbl
中绑定的数量。它花费恒定的时间。多次绑定只计算一次,因此 Hashtbl.length
给出了 Hashtbl.iter
调用其第一个参数的次数。
val randomize : unit -> unit
在调用 Hashtbl.randomize()
之后,默认情况下会在随机化模式下创建哈希表:Hashtbl.create
返回随机化的哈希表,除非给出了 ~random:false
可选参数。可以通过在 OCAMLRUNPARAM
环境变量中设置 R
参数来实现相同的效果。
建议需要保护自己免受 Hashtbl.create
中描述的拒绝服务攻击的应用程序或 Web 框架在初始化时在创建任何域之前调用 Hashtbl.randomize()
。
请注意,一旦调用了 Hashtbl.randomize()
,就无法恢复到 Hashtbl.create
的非随机化默认行为。这是故意的。仍然可以使用 Hashtbl.create ~random:false
创建非随机化的哈希表。
val is_randomized : unit -> bool
如果表当前默认在随机化模式下创建,则返回 true
,否则返回 false
。
val rebuild : ?random:bool -> ('a, 'b) t -> ('a, 'b) t
返回给定哈希表的副本。与 Hashtbl.copy
不同,Hashtbl.rebuild
h
会重新散列原始表 h
的所有 (键,值) 条目。如果 h
已随机化,或者可选的 random
参数为 true,或者如果默认情况下创建随机化的哈希表,则返回的哈希表将被随机化;有关更多信息,请参见 Hashtbl.create
。
Hashtbl.rebuild
可以安全地用于导入由旧版本的 Hashtbl
模块构建的哈希表,然后将其编组到持久存储中。解组后,应用 Hashtbl.rebuild
以生成当前版本的 Hashtbl
模块的哈希表。
type
statistics = {
|
num_bindings : |
(* | 表中存在的绑定数量。与 | *) |
|
num_buckets : |
(* | 表中桶的数量。 | *) |
|
max_bucket_length : |
(* | 每个桶的最大绑定数。 | *) |
|
bucket_histogram : |
(* | 桶大小的直方图。此数组 | *) |
}
val stats : ('a, 'b) t -> statistics
Hashtbl.stats tbl
返回关于表 tbl
的统计信息:桶的数量、最大桶的大小、桶按大小的分布。
val to_seq : ('a, 'b) t -> ('a * 'b) Seq.t
遍历整个表。绑定在序列中出现的顺序未指定。但是,如果表包含多个相同键的绑定,则它们以引入的反序出现,即最新的绑定最先出现。
如果在迭代期间修改哈希表,则行为未指定。
val to_seq_keys : ('a, 'b) t -> 'a Seq.t
与 Seq.map fst (to_seq m)
相同
val to_seq_values : ('a, 'b) t -> 'b Seq.t
与 Seq.map snd (to_seq m)
相同
val add_seq : ('a, 'b) t -> ('a * 'b) Seq.t -> unit
使用 Hashtbl.add
将给定的绑定添加到表中。
val replace_seq : ('a, 'b) t -> ('a * 'b) Seq.t -> unit
使用 Hashtbl.replace
将给定的绑定添加到表中。
val of_seq : ('a * 'b) Seq.t -> ('a, 'b) t
根据给定的绑定构建一个表。绑定以它们在序列中出现的相同顺序添加,使用 Hashtbl.replace_seq
,这意味着如果两个键值对具有相同的键,则只有最后一个键值对会出现在表中。
函子接口允许使用特定的比较和哈希函数,无论是出于性能/安全考虑,还是因为键无法使用多态内置函数进行哈希/比较。
例如,可能需要为整数键专门化一个表。
module IntHash =
struct
type t = int
let equal i j = i=j
let hash i = i land max_int
end
module IntHashtbl = Hashtbl.Make(IntHash)
let h = IntHashtbl.create 17 in
IntHashtbl.add h 12 "hello"
这将创建一个新的模块 IntHashtbl
,并带有一个新的类型 'a
,该类型表示从
IntHashtbl.tint
到 'a
的表。在此示例中,h
包含 string
值,因此其类型为 string IntHashtbl.t
。
请注意,新类型 'a IntHashtbl.t
与通用接口的类型 ('a,'b) Hashtbl.t
不兼容。例如,Hashtbl.length h
将无法通过类型检查,您必须使用 IntHashtbl.length
。
module type HashedType =sig
..end
函子 Hashtbl.Make
的输入签名。
module type S =sig
..end
函子 Hashtbl.Make
的输出签名。
module Make:
构建哈希表结构实现的函子。
module type SeededHashedType =sig
..end
函子 Hashtbl.MakeSeeded
的输入签名。
module type SeededS =sig
..end
函子 Hashtbl.MakeSeeded
的输出签名。
module MakeSeeded:
构建哈希表结构实现的函子。
val hash : 'a -> int
Hashtbl.hash x
将一个非负整数与任何类型的任何值关联。保证如果 x = y
或 Stdlib.compare x y = 0
,则 hash x = hash y
。此外,hash
始终终止,即使在循环结构上也是如此。
val seeded_hash : int -> 'a -> int
Hashtbl.hash
的一个变体,它由一个整数种子进一步参数化。
val hash_param : int -> int -> 'a -> int
Hashtbl.hash_param meaningful total x
计算 x
的哈希值,具有与 hash
相同的属性。两个额外的整数参数 meaningful
和 total
提供了对哈希的更精确控制。哈希执行结构 x
的广度优先、从左到右的遍历,在遇到 meaningful
个有意义的节点或 total
个节点(有意义或无意义)后停止。如果用户指定的 total
超过某个值(当前为 256),则将其限制为该值。有意义的节点是:整数;浮点数;字符串;字符;布尔值;以及常量构造函数。较大的 meaningful
和 total
值意味着在计算最终哈希值时会考虑更多节点,因此碰撞发生的可能性较小。但是,哈希需要更长的时间。参数 meaningful
和 total
控制准确性和速度之间的权衡。作为默认选择,Hashtbl.hash
和 Hashtbl.seeded_hash
使用 meaningful = 10
和 total = 100
。
val seeded_hash_param : int -> int -> int -> 'a -> int
Hashtbl.hash_param
的一个变体,它由一个整数种子进一步参数化。用法:Hashtbl.seeded_hash_param meaningful total seed x
。
(* 0...99 *)
let seq = Seq.ints 0 |> Seq.take 100
(* build from Seq.t *)
# let tbl =
seq
|> Seq.map (fun x -> x, string_of_int x)
|> Hashtbl.of_seq
val tbl : (int, string) Hashtbl.t = <abstr>
# Hashtbl.length tbl
- : int = 100
# Hashtbl.find_opt tbl 32
- : string option = Some "32"
# Hashtbl.find_opt tbl 166
- : string option = None
# Hashtbl.replace tbl 166 "one six six"
- : unit = ()
# Hashtbl.find_opt tbl 166
- : string option = Some "one six six"
# Hashtbl.length tbl
- : int = 101
给定一个元素序列(此处为 Seq.t
),我们希望计算每个不同元素在序列中出现的次数。一个简单的执行此操作的方法(假设元素是可比较且可哈希的)是使用一个哈希表,该表将元素映射到它们的出现次数。
这里我们使用一个(ascii)字符序列(类型 char
)来说明该原理。我们使用一个为 char
专门定制的 Char_tbl
。
# module Char_tbl = Hashtbl.Make(struct
type t = char
let equal = Char.equal
let hash = Hashtbl.hash
end)
(* count distinct occurrences of chars in [seq] *)
# let count_chars (seq : char Seq.t) : _ list =
let counts = Char_tbl.create 16 in
Seq.iter
(fun c ->
let count_c =
Char_tbl.find_opt counts c
|> Option.value ~default:0
in
Char_tbl.replace counts c (count_c + 1))
seq;
(* turn into a list *)
Char_tbl.fold (fun c n l -> (c,n) :: l) counts []
|> List.sort (fun (c1,_)(c2,_) -> Char.compare c1 c2)
val count_chars : Char_tbl.key Seq.t -> (Char.t * int) list = <fun>
(* basic seq from a string *)
# let seq = String.to_seq "hello world, and all the camels in it!"
val seq : char Seq.t = <fun>
# count_chars seq
- : (Char.t * int) list =
[(' ', 7); ('!', 1); (',', 1); ('a', 3); ('c', 1); ('d', 2); ('e', 3);
('h', 2); ('i', 2); ('l', 6); ('m', 1); ('n', 2); ('o', 2); ('r', 1);
('s', 1); ('t', 2); ('w', 1)]
(* "abcabcabc..." *)
# let seq2 =
Seq.cycle (String.to_seq "abc") |> Seq.take 31
val seq2 : char Seq.t = <fun>
# String.of_seq seq2
- : String.t = "abcabcabcabcabcabcabcabcabcabca"
# count_chars seq2
- : (Char.t * int) list = [('a', 11); ('b', 10); ('c', 10)]