地图

简介

Map 模块允许您为您的类型创建不可变的键值关联表。此类地图永远不会被修改,并且每个操作都会返回一个新的地图。

注意:本教程中描述的地图不应与诸如List.mapArray.mapOption.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值,其类型在其类型中有一个类型参数'aempty : '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_optfind函数

# 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_optfind_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_firstfind_last的行为类似,除了它们抛出异常而不是返回选项。

请注意,find_first_optfind_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函子,前提是它实现了以下两点

  1. 一个没有类型参数的t类型
  2. 一个比较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

仍然需要帮助?

帮助改进我们的文档

所有OCaml文档都是开源的。发现错误或不清楚的地方?提交拉取请求。

OCaml

创新。社区。安全。