模块 MoreLabels.Hashtbl

module Hashtbl: sig .. end

哈希表和哈希函数。

哈希表是哈希关联表,具有就地修改功能。由于大多数对哈希表的操作都会修改其输入,因此它们更常用于命令式代码。查找与键关联的值(参见 MoreLabels.Hashtbl.findMoreLabels.Hashtbl.find_opt)通常非常快,通常比在 MoreLabels.Map 中查找等效值更快。

当性能或灵活性至关重要时,可以使用函子 MoreLabels.Hashtbl.MakeMoreLabels.Hashtbl.MakeSeeded。用户为键类型提供自定义的相等性和哈希函数,并获得针对此特定键类型的自定义哈希表类型。

警告 哈希表的好坏取决于哈希函数。不好的哈希函数会将表变成退化的关联列表,具有线性时间查找而不是常数时间查找。

多态 MoreLabels.Hashtbl.t 哈希表在更简单的用例或交互式环境中很有用。它使用在 OCaml 运行时定义的多态 MoreLabels.Hashtbl.hash 函数(在撰写本文时,它是 SipHash),以及多态相等性 (=)

参见 示例部分

非同步访问

对哈希表的非同步访问可能会导致哈希表状态无效。因此,对哈希表的并发访问必须同步(例如,使用 Mutex.t)。

泛型接口

type ('a, 'b) t = ('a, 'b) Hashtbl.t 

从类型 'a 到类型 'b 的哈希表的类型。

val create : ?random:bool -> int -> ('a, 'b) t

Hashtbl.create n 创建一个新的空哈希表,其初始大小为 n。为了获得最佳效果,n 应该与表中预期元素的数量处于同一数量级。表会根据需要增长,因此 n 只是一个初始猜测。

可选的 ~random 参数(布尔值)控制在每次执行 Hashtbl.create 时哈希表的内部组织是随机的还是在所有执行中都是确定的。

使用 ~random 设置为 false 创建的哈希表使用固定哈希函数 (MoreLabels.Hashtbl.hash) 将键分配到桶中。因此,键之间的冲突会以确定性的方式发生。在面向 Web 的应用程序或其他安全敏感应用程序中,恶意用户可以利用确定性的冲突模式来创建拒绝服务攻击:攻击者发送精心制作的输入以在表中创建许多冲突,从而减慢应用程序的速度。

使用 ~random 设置为 true 创建的哈希表使用带种子的哈希函数 MoreLabels.Hashtbl.seeded_hash,该函数使用在哈希表创建时随机选择的种子。实际上,使用的哈希函数是在 2^{30} 个不同哈希函数中随机选择的。所有这些哈希函数都有不同的冲突模式,使上述拒绝服务攻击无效。但是,由于随机化,使用 MoreLabels.Hashtbl.foldMoreLabels.Hashtbl.iter 枚举哈希表的所有元素不再是确定性的:元素在程序的不同运行中以不同的顺序枚举。

如果未提供 ~random 参数,则默认情况下,哈希表将以非随机模式创建。此默认值可以通过以下方式更改:通过调用 MoreLabels.Hashtbl.randomize 以编程方式更改,或者通过在 OCAMLRUNPARAM 环境变量中设置 R 标志。

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 -> key:'a -> data:'b -> unit

Hashtbl.add tbl ~key ~data 在表 tbl 中添加 keydata 的绑定。

警告key 的先前绑定不会被删除,而只是被隐藏。也就是说,在执行 MoreLabels.Hashtbl.remove tbl key 后,key 的先前绑定(如果有)将被恢复。(与关联列表的行为相同。)

如果您希望使用替换元素的经典行为,请参见 MoreLabels.Hashtbl.replace

val find : ('a, 'b) t -> 'a -> 'b

Hashtbl.find tbl x 返回 xtbl 中的当前绑定,如果不存在这样的绑定,则引发 Not_found

val find_opt : ('a, 'b) t -> 'a -> 'b option

Hashtbl.find_opt tbl x 返回 xtbl 中的当前绑定,如果不存在这样的绑定,则返回 None

val find_all : ('a, 'b) t -> 'a -> 'b list

Hashtbl.find_all tbl x 返回与 xtbl 中关联的所有数据的列表。当前绑定首先返回,然后是以前的绑定,以在表中引入的逆序排列。

val mem : ('a, 'b) t -> 'a -> bool

Hashtbl.mem tbl x 检查 x 是否在 tbl 中绑定。

val remove : ('a, 'b) t -> 'a -> unit

Hashtbl.remove tbl x 删除 xtbl 中的当前绑定,如果存在,则恢复以前的绑定。如果 xtbl 中未绑定,则不执行任何操作。

val replace : ('a, 'b) t -> key:'a -> data:'b -> unit

Hashtbl.replace tbl ~key ~datakeytbl 中的当前绑定替换为 keydata 的绑定。如果 keytbl 中未绑定,则将 keydata 的绑定添加到 tbl 中。这在功能上等效于 MoreLabels.Hashtbl.remove tbl key 后跟 MoreLabels.Hashtbl.add tbl key data

val iter : f:(key:'a -> data:'b -> unit) -> ('a, 'b) t -> unit

Hashtbl.iter ~f tblf 应用于表 tbl 中的所有绑定。 f 以键作为第一个参数,以关联的值作为第二个参数接收。每个绑定都准确地向 f 展示一次。

f 传递绑定的顺序未指定。但是,如果表中包含多个针对同一个键的绑定,则它们将以相反的引入顺序传递给 f,即最晚的绑定将首先传递。

如果哈希表是在非随机模式下创建的,则枚举绑定的顺序在程序的后续运行之间是可重复的,甚至在 OCaml 的小版本之间也是可重复的。对于随机化哈希表,枚举顺序完全是随机的。

如果在迭代期间 f 修改了哈希表,则行为未指定。

val filter_map_inplace : f:(key:'a -> data:'b -> 'b option) -> ('a, 'b) t -> unit

Hashtbl.filter_map_inplace ~f tblf 应用于表 tbl 中的所有绑定,并根据 f 的结果更新每个绑定。如果 f 返回 None,则绑定将被丢弃。如果它返回 Some new_val,则绑定将更新为将键与 new_val 关联。

有关 MoreLabels.Hashtbl.iter 的其他评论也适用。

val fold : f:(key:'a -> data:'b -> 'acc -> 'acc) ->
('a, 'b) t -> init:'acc -> 'acc

Hashtbl.fold ~f tbl ~init 计算 (f kN dN ... (f k1 d1 init)...),其中 k1 ... kNtbl 中所有绑定的键,而 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() 后,哈希表默认情况下将以随机模式创建:MoreLabels.Hashtbl.create 返回随机化哈希表,除非提供了 ~random:false 可选参数。通过在 OCAMLRUNPARAM 环境变量中设置 R 参数,可以获得相同的效果。

建议需要保护自己免受 MoreLabels.Hashtbl.create 中描述的拒绝服务攻击的应用程序或 Web 框架在初始化期间在创建任何域之前调用 Hashtbl.randomize()

请注意,一旦调用了 Hashtbl.randomize(),就无法恢复到 MoreLabels.Hashtbl.create 的非随机化默认行为。这是故意的。非随机化哈希表仍然可以使用 Hashtbl.create ~random:false 创建。

val is_randomized : unit -> bool

如果表当前默认情况下以随机模式创建,则返回 true,否则返回 false

val rebuild : ?random:bool ->
('a, 'b) t -> ('a, 'b) t

返回给定哈希表的副本。与 MoreLabels.Hashtbl.copy 不同,MoreLabels.Hashtbl.rebuild h 将原始表 h 的所有 (key, value) 条目重新哈希。如果 h 已被随机化,或者可选的 random 参数为 true,或者默认情况下是创建随机化哈希表,则返回的哈希表将被随机化;有关更多信息,请参见 MoreLabels.Hashtbl.create

MoreLabels.Hashtbl.rebuild 可以安全地用于导入由旧版本的 MoreLabels.Hashtbl 模块构建的哈希表,然后将其编组到持久存储中。解组后,应用 MoreLabels.Hashtbl.rebuild 以生成当前版本的 MoreLabels.Hashtbl 模块的哈希表。

type statistics = Hashtbl.statistics = {
   num_bindings : int; (*

表中存在的绑定数量。与 MoreLabels.Hashtbl.length 返回的值相同。

*)
   num_buckets : int; (*

表中的桶数。

*)
   max_bucket_length : int; (*

每个桶的最大绑定数。

*)
   bucket_histogram : int array; (*

桶大小的直方图。此数组 histo 的长度为 max_bucket_length + 1histo.(i) 的值为大小为 i 的桶数。

*)
}
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

使用 MoreLabels.Hashtbl.add 将给定绑定添加到表中。

val replace_seq : ('a, 'b) t -> ('a * 'b) Seq.t -> unit

使用 MoreLabels.Hashtbl.replace 将给定绑定添加到表中。

val of_seq : ('a * 'b) Seq.t -> ('a, 'b) t

从给定绑定构建表。绑定按其在序列中出现的顺序添加,使用 MoreLabels.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.t
,用于从 int'a 的表。在本例中,h 包含 string 值,因此其类型为 string IntHashtbl.t

请注意,新类型 'IntHashtbl.t 与泛型接口的类型 ('a,'b) Hashtbl.t 不兼容。例如,Hashtbl.length h 将无法进行类型检查,必须使用 IntHashtbl.length

module type HashedType = sig .. end

函子 MoreLabels.Hashtbl.Make 的输入签名。

module type S = sig .. end

函子 MoreLabels.Hashtbl.Make 的输出签名。

module Make: 
functor (H : HashedType-> S with type key = H.t and type 'a t = 'a Hashtbl.Make(H).t

构建哈希表结构实现的函子。

module type SeededHashedType = sig .. end

函子 MoreLabels.Hashtbl.MakeSeeded 的输入签名。

module type SeededS = sig .. end

函子 MoreLabels.Hashtbl.MakeSeeded 的输出签名。

module MakeSeeded: 
functor (H : SeededHashedType-> SeededS with type key = H.t and type 'a t = 'a Hashtbl.MakeSeeded(H).t

构建哈希表结构实现的函子。

多态哈希函数

val hash : 'a -> int

Hashtbl.hash x 将一个非负整数与任何类型的值相关联。保证如果 x = yStdlib.compare x y = 0,则 hash x = hash y。此外,hash 始终终止,即使在循环结构上也是如此。

val seeded_hash : int -> 'a -> int

MoreLabels.Hashtbl.hash 的变体,进一步由整数种子参数化。

val hash_param : int -> int -> 'a -> int

Hashtbl.hash_param meaningful total x 计算 x 的哈希值,具有与 hash 相同的属性。两个额外的整数参数 meaningfultotal 提供对哈希的更精确控制。哈希对结构 x 执行广度优先、从左到右的遍历,在遇到 meaningful 个有意义的节点或遇到 total 个节点(有意义的或无意义的)后停止。如果用户指定的 total 超过某个值(目前为 256),则将其限制为该值。有意义的节点是:整数;浮点数;字符串;字符;布尔值;以及常量构造函数。 meaningfultotal 的值越大,意味着计算最终哈希值时考虑的节点越多,因此发生冲突的可能性更小。但是,哈希需要更长时间。参数 meaningfultotal 控制准确性和速度之间的权衡。作为默认选择, MoreLabels.Hashtbl.hashMoreLabels.Hashtbl.seeded_hash 采用 meaningful = 10total = 100

val seeded_hash_param : int -> int -> int -> 'a -> int

MoreLabels.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)]