Maps
Introduction
The Map
module lets you create immutable key-value association
tables for your types. Such
maps are never modified, and every operation returns a new map instead.
Note: Maps described in this tutorial should not be confused with map
functions such as List.map
, Array.map
, Option.map
, and others. The maps
described in this tutorial are also called dictionaries or associative tables.
To use Map
, we first have to use the Map.Make
functor to create our custom
map module. Refer to the Functors for more information on
functors. This functor has a module parameter that defines the keys' type to
be used in the maps, and a function for comparing them.
# 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
After naming the newly-created module StringMap
, OCaml's toplevel displays the
module's signature. Since it contains a large number of functions, the output
copied here is shortened for brevity (...)
.
This module doesn't define the values' type. It will be defined when we create our first map.
Creating a Map
The StringMap
module has an empty
value that has a type parameter 'a
in
its type: empty : 'a t
.
This means that we can use empty
to create new empty maps where the value is of any type.
# 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>
The type of the values can be specified in two ways:
- When you create your new map with an annotation
- By adding an element to the map:
# let int_map = StringMap.(empty |> add "one" 1);;
val int_map : int StringMap.t = <abstr>
Working With Maps
Throughout the rest of this tutorial, we use the following map:
# let lucky_numbers = StringMap.of_seq @@ List.to_seq [
("leostera", 2112);
("charstring88", 88);
("divagnz", 13);
];;
val lucky_numbers : int StringMap.t = <abstr>
Finding Entries in a Map
To find entries in a map, use the find_opt
or find
functions:
# StringMap.find_opt "leostera" lucky_numbers;;
- : int option = Some 2112
# StringMap.find "leostera" lucky_numbers;;
- : int = 2112
When the searched key is present from the map:
find_opt
returns the associated value, wrapped in an optionfind
returns the associated
When the searched key is absent from the map:
find_opt
returnsNone
find
throws theNot_found
exceptions
We can also use find_first_opt
and find_last_opt
if we want to use a
predicate function:
# 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)
The functions find_first
and find_last
behave similarly, except they
throw exceptions instead of returning options.
Note that find_first_opt
and find_last_opt
return the key-value pair,
not just the value.
Adding Entries to a Map
To add an entry to a map, use the add
function that takes a key, a value, and
a map. It returns a new map with that key-value pair added:
# 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
If the passed key is already associated with a value, the passed value replaces it.
Note that the initial map lucky_numbers
remains unchanged.
Removing Entries From a Map
To remove an entry from a map, use the remove
function, which takes a key and
a map. It returns a new map with that key's entry removed.
# 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
Removing a key that isn't present in the map has no effect.
Note that the initial map lucky_numbers
remains unchanged.
Changing the Value Associated With a Key
To change a key's associated value, use the update
function. It takes a
key, a map, and an update function. It returns a new map with the key's
associated value replaced by the new one.
# 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
You should experiment with different update functions; several behaviors are possible.
Checking if a Key is Contained in a Map
To check if a map contains a key, use the mem
function:
# StringMap.mem "paguzar" less_lucky_numbers;;
- : bool = false
Merging Maps
To merge two maps, use the union
function, which takes two maps, and a
function deciding how to handle entries with identical keys. It returns a new map.
Note that the input maps are not modified.
# StringMap.union;;
- : (string -> 'a -> 'a -> 'a option) ->
'a StringMap.t -> 'a StringMap.t -> 'a StringMap.t
= <fun>
Here are examples of duplicate key resolution functions:
# 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
picks the result's value from the first mappick_snd
picks the result's value from the second mapdrop
drops both entries in the result map
# 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
Filtering a Map
To filter a map, use the filter
function. It takes a predicate to filter
entries and a map. It returns a new map containing the entries satisfying the
predicate.
# let even_numbers =
StringMap.filter
(fun _ number -> number mod 2 = 0)
lucky_numbers;;
val even_numbers : int StringMap.t = <abstr>
Map a Map
Map modules have a map
function:
StringMap.map;;
- : ('a -> 'b) -> 'a StringMap.t -> 'b StringMap.t = <fun>
The lucky_numbers
map associates string keys with integer values.
# lucky_numbers;;
- : int StringMap.t = <abstr>
Using StringMap.map
, we create a map associating keys with string values:
# let lucky_strings = StringMap.map string_of_int lucky_numbers;;
val lucky_strings : string StringMap.t = <abstr>
The keys are the same in both maps. For each key, a value in lucky_numbers
is converted into a value in lucky_strings
using string_of_int
.
# lucky_numbers |> StringMap.find "leostera" |> string_of_int;;
- : string = "2112"
# lucky_strings |> StringMap.find "leostera";;
- : string = "2112"
Maps With Custom Key Types
If you need to create a map with a custom key type, you can call the Map.Make
functor with a module of your own, provided that it implements two things:
- A type
t
type with no type parameters - A function
compare : t -> t -> int
function that comparest
values
Let's define our custom map below for non-negative numbers.
We'll start by defining a module for strings that compares them in case-insensitive way.
# 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
Note that our module has a type t
and also a compare
function. Now we can
call the Map.Make
functor to get a map for non-negative numbers:
# 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
Conclusion
This was an overview of OCaml's Map
module. Maps are reasonably efficient and
can be an alternative to the imperative Hashtbl
module.
For more information, refer to Map in the Standard Library documentation.
Help Improve Our Documentation
All OCaml docs are open source. See something that's wrong or unclear? Submit a pull request.