module Hashtbl:sig
..end
雜湊表與雜湊函數。
雜湊表是雜湊關聯表,具有原地修改的功能。由於對雜湊表的大多數操作都會修改其輸入,因此它們更常在命令式程式碼中使用。查找與鍵關聯的值(請參閱 MoreLabels.Hashtbl.find
、MoreLabels.Hashtbl.find_opt
)通常非常快,通常比在 MoreLabels.Map
中進行等效查找更快。
當效能或彈性是關鍵時,可以使用函子 MoreLabels.Hashtbl.Make
和 MoreLabels.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.fold
或 MoreLabels.Hashtbl.iter
列舉雜湊表的所有元素不再是確定性的:元素在程式的不同執行中以不同的順序列舉。
如果未提供 ~random
參數,則預設情況下會在非隨機模式下建立雜湊表。此預設值可以透過呼叫 MoreLabels.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 -> key:'a -> data:'b -> unit
Hashtbl.add tbl ~key ~data
在表格 tbl
中新增 key
到 data
的繫結。
警告:先前對於 key
的繫結不會被移除,而只是被隱藏。也就是說,在執行 MoreLabels.Hashtbl.remove
tbl key
之後,如果存在,則會還原先前對於 key
的繫結。(與關聯列表的行為相同。)
如果您想要替換元素的經典行為,請參閱 MoreLabels.Hashtbl.replace
。
val find : ('a, 'b) t -> 'a -> 'b
Hashtbl.find tbl x
傳回 x
在 tbl
中的目前繫結,如果不存在此類繫結,則會引發 Not_found
。
val find_opt : ('a, 'b) t -> 'a -> 'b option
Hashtbl.find_opt tbl x
傳回 x
在 tbl
中的目前繫結,如果不存在此類繫結,則傳回 None
。
val find_all : ('a, 'b) t -> 'a -> 'b list
Hashtbl.find_all tbl x
傳回與 x
在 tbl
中關聯的所有資料的列表。首先傳回目前的繫結,然後按在表格中引入的反向順序傳回先前的繫結。
val mem : ('a, 'b) t -> 'a -> bool
Hashtbl.mem tbl x
檢查 x
是否在 tbl
中繫結。
val remove : ('a, 'b) t -> 'a -> unit
Hashtbl.remove tbl x
移除 x
在 tbl
中的目前繫結,如果存在,則會還原先前的繫結。如果 x
未在 tbl
中繫結,則它不會執行任何動作。
val replace : ('a, 'b) t -> key:'a -> data:'b -> unit
Hashtbl.replace tbl ~key ~data
將 key
在 tbl
中的目前繫結替換為 key
到 data
的繫結。如果 key
未在 tbl
中繫結,則會將 key
到 data
的繫結新增到 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 tbl
將 f
應用於表格 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 tbl
將 f
應用於表格 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 ... 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()
之後,預設情況下會在隨機模式下建立雜湊表: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
模組的雜湊表。
typestatistics =
Hashtbl.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
使用 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.tint
到 'a
的表格。在此範例中,h
包含 string
值,因此其類型為 string IntHashtbl.t
。
請注意,新的類型 'a 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:
建立雜湊表結構實作的函子。
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 = y
或 Stdlib.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
相同。兩個額外的整數參數 meaningful
和 total
提供對雜湊更精確的控制。雜湊會執行 x
結構的廣度優先、由左至右的走訪,在遇到 meaningful
個有意義節點或遇到 total
個節點(有意義或無意義)後停止。如果使用者指定的 total
超過某個值 (目前為 256),則會將其上限設為該值。有意義的節點為:整數、浮點數、字串、字元、布林值和常數建構函式。meaningful
和 total
的值越大,表示會納入更多節點來計算最終雜湊值,因此發生碰撞的可能性越低。但是,雜湊需要較長的時間。參數 meaningful
和 total
會控制準確性和速度之間的權衡。作為預設選項,MoreLabels.Hashtbl.hash
和 MoreLabels.Hashtbl.seeded_hash
會取 meaningful = 10
和 total = 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)]