模組 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 時隨機化,或者在所有執行中都是確定性的。

使用設定為 false~random 建立的雜湊表使用固定的雜湊函數 (MoreLabels.Hashtbl.hash) 來在儲存桶之間分配鍵。因此,鍵之間的衝突會確定性地發生。在面向 Web 的應用程式或其他對安全性敏感的應用程式中,惡意使用者可以利用確定性的衝突模式來建立阻斷服務攻擊:攻擊者傳送精心設計的輸入以在表中產生許多衝突,從而減慢應用程式的速度。

使用設定為 true~random 建立的雜湊表使用種子雜湊函數 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 中的目前繫結,如果存在,則會還原先前的繫結。如果 x 未在 tbl 中繫結,則它不會執行任何動作。

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

Hashtbl.replace tbl ~key ~datakeytbl 中的目前繫結替換為 keydata 的繫結。如果 key 未在 tbl 中繫結,則會將 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 的所有 (鍵, 值) 條目。如果 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)]