地图
简介
Map
模块允许您为您的类型创建不可变的键值关联表。此类地图永远不会被修改,并且每个操作都会返回一个新的地图。
注意:本教程中描述的地图不应与诸如List.map
、Array.map
、Option.map
等映射函数混淆。本教程中描述的地图也称为字典或关联表。
要使用Map
,我们首先必须使用Map.Make
函子来创建我们自定义的地图模块。有关函子的更多信息,请参阅函子。此函子具有一个模块参数,该参数定义了地图中使用的键的类型,以及一个用于比较它们的函数。
# module StringMap = Map.Make(String);;
module StringMap :
sig
type key = string
type 'a t = 'a Map.Make(String).t
val empty : 'a t
val add : key -> 'a -> 'a t -> 'a t
val add_to_list : key -> 'a -> 'a list t -> 'a list t
val update : key -> ('a option -> 'a option) -> 'a t -> 'a t
val singleton : key -> 'a -> 'a t
val remove : key -> 'a t -> 'a t
(* ... *)
end
在将新创建的模块命名为StringMap
后,OCaml 的顶层显示了该模块的签名。由于它包含大量函数,因此此处复制的输出为了简洁起见进行了缩短(...)
。
此模块未定义值的类型。当我们创建第一个地图时,将定义它。
创建地图
StringMap
模块有一个empty
值,其类型在其类型中有一个类型参数'a
:empty : 'a t
。
这意味着我们可以使用empty
创建新的空地图,其中值可以是任何类型。
# let int_map : int StringMap.t = StringMap.empty;;
val int_map : int StringMap.t = <abstr>
# let float_map : float StringMap.t = StringMap.empty;;
val float_map : float StringMap.t = <abstr>
值的类型可以通过两种方式指定
- 当您使用注释创建新地图时
- 通过向地图添加元素
# let int_map = StringMap.(empty |> add "one" 1);;
val int_map : int StringMap.t = <abstr>
使用地图
在本教程的其余部分,我们使用以下地图
# let lucky_numbers = StringMap.of_seq @@ List.to_seq [
("leostera", 2112);
("charstring88", 88);
("divagnz", 13);
];;
val lucky_numbers : int StringMap.t = <abstr>
在地图中查找条目
要在地图中查找条目,请使用find_opt
或find
函数
# StringMap.find_opt "leostera" lucky_numbers;;
- : int option = Some 2112
# StringMap.find "leostera" lucky_numbers;;
- : int = 2112
当从地图中找到搜索的键时
find_opt
返回关联的值,包装在一个选项中find
返回关联的
当从地图中找不到搜索的键时
find_opt
返回None
find
抛出Not_found
异常
如果我们想使用谓词函数,我们也可以使用find_first_opt
和find_last_opt
# let first_under_10_chars : (string * int) option =
StringMap.find_first_opt
(fun key -> String.length key < 10)
lucky_numbers;;
val first_under_10_chars : (string * int) option = Some ("divagnz", 13)
函数find_first
和find_last
的行为类似,除了它们抛出异常而不是返回选项。
请注意,find_first_opt
和find_last_opt
返回键值对,而不仅仅是值。
向地图添加条目
要向地图添加条目,请使用add
函数,该函数接受一个键、一个值和一个地图。它返回一个添加了该键值对的新地图
# let more_lucky_numbers = lucky_numbers |> StringMap.add "paguzar" 108;;
val more_lucky_numbers : int StringMap.t = <abstr>
# StringMap.find_opt "paguzar" lucky_numbers;;
- : int option = None
# StringMap.find_opt "paguzar" more_lucky_numbers;;
- : int option = Some 108
如果传递的键已与某个值关联,则传递的值将替换它。
请注意,初始地图lucky_numbers
保持不变。
从地图中删除条目
要从地图中删除条目,请使用remove
函数,该函数接受一个键和一个地图。它返回一个删除了该键条目的新地图。
# let less_lucky_numbers = lucky_numbers |> StringMap.remove "divagnz";;
val less_lucky_numbers : int StringMap.t = <abstr>
# StringMap.find_opt "divagnz" lucky_numbers;;
- : int option = Some 13
# StringMap.find_opt "divagnz" less_lucky_numbers;;
- : int option = None
删除地图中不存在的键不会产生任何影响。
请注意,初始地图lucky_numbers
保持不变。
更改与键关联的值
要更改键的关联值,请使用update
函数。它接受一个键、一个地图和一个更新函数。它返回一个用新值替换了键的关联值的新地图。
# let updated_lucky_numbers =
lucky_numbers
|> StringMap.update "charstring88" (Option.map (fun _ -> 99));;
val updated_lucky_numbers : int StringMap.t = <abstr>
# StringMap.find_opt "charstring88" lucky_numbers;;
- : int option = Some 88
# StringMap.find_opt "charstring88" updated_lucky_numbers;;
- : int option = Some 99
您应该尝试不同的更新函数;多种行为是可能的。
检查地图中是否包含键
要检查地图是否包含键,请使用mem
函数
# StringMap.mem "paguzar" less_lucky_numbers;;
- : bool = false
合并地图
要合并两个映射,可以使用union
函数,该函数接收两个映射和一个用于决定如何处理键相同的条目的函数。它返回一个新的映射。请注意,输入映射不会被修改。
# StringMap.union;;
- : (string -> 'a -> 'a -> 'a option) ->
'a StringMap.t -> 'a StringMap.t -> 'a StringMap.t
= <fun>
以下是重复键解析函数的示例
# let pick_fst key v1 _ = Some v1;;
val pick_fst : 'a -> 'b -> 'c -> 'b option = <fun>
# let pick_snd key _ v2 = Some v2;;
val pick_snd : 'a -> 'b -> 'c -> 'c option = <fun>
# let drop _ _ _ = None;;
val drop : 'a -> 'b -> 'c -> 'd option = <fun>
pick_fst
从第一个映射中选择结果的值pick_snd
从第二个映射中选择结果的值drop
在结果映射中删除这两个条目
# StringMap.(
union pick_fst lucky_numbers updated_lucky_numbers
|> find_opt "charstring88"
);;
- : int option = Some 88
# StringMap.(
union pick_snd lucky_numbers updated_lucky_numbers
|> find_opt "charstring88"
);;
- : int option = Some 99
# StringMap.(
union drop lucky_numbers updated_lucky_numbers
|> find_opt "charstring88"
);;
- : int option = None
过滤映射
要过滤映射,可以使用filter
函数。它接收一个用于过滤条目的谓词和一个映射。它返回一个包含满足谓词的条目的新映射。
# let even_numbers =
StringMap.filter
(fun _ number -> number mod 2 = 0)
lucky_numbers;;
val even_numbers : int StringMap.t = <abstr>
映射映射
Map 模块有一个map
函数
StringMap.map;;
- : ('a -> 'b) -> 'a StringMap.t -> 'b StringMap.t = <fun>
lucky_numbers
映射将字符串键与整数值关联。
# lucky_numbers;;
- : int StringMap.t = <abstr>
使用StringMap.map
,我们创建了一个将键与字符串值关联的映射
# let lucky_strings = StringMap.map string_of_int lucky_numbers;;
val lucky_strings : string StringMap.t = <abstr>
这两个映射中的键相同。对于每个键,lucky_numbers
中的值使用string_of_int
转换为lucky_strings
中的值。
# lucky_numbers |> StringMap.find "leostera" |> string_of_int;;
- : string = "2112"
# lucky_strings |> StringMap.find "leostera";;
- : string = "2112"
具有自定义键类型的映射
如果您需要创建具有自定义键类型的映射,您可以使用您自己的模块调用Map.Make
函子,前提是它实现了以下两点
- 一个没有类型参数的
t
类型 - 一个比较
t
值的函数compare : t -> t -> int
让我们在下面为非负数定义我们的自定义映射。
我们将首先为字符串定义一个模块,以不区分大小写的方式比较它们。
# module Istring : sig
type t
val compare : t -> t -> int
end = struct
type t = string
let compare a b = String.(compare (lowercase_ascii a) (lowercase_ascii b))
end;;
module Istring : sig type t val compare : t -> t -> int end
请注意,我们的模块有一个type t
和一个compare
函数。现在我们可以调用Map.Make
函子来获取非负数的映射
# module IstringMap = Map.Make(Istring);;
module IstringMap :
sig
type key = Istring.t
type 'a t = 'a Map.Make(Istring).t
val empty : 'a t
val is_empty : 'a t -> bool
val mem : key -> 'a t -> bool
val add : key -> 'a -> 'a t -> 'a t
val update : key -> ('a option -> 'a option) -> 'a t -> 'a t
val singleton : key -> 'a -> 'a t
val remove : key -> 'a t -> 'a t
(* ... *)
end
结论
这是OCaml的Map
模块的概述。映射效率相当高,可以作为命令式Hashtbl
模块的替代方案。
有关更多信息,请参阅标准库文档中的Map。