Maps
Maps are a data structure that associates keys to values. Together, a key and its value make up a binding, also called an element. The keys must all be the same type and the values must be the same type. Keys must be unique within a map.
The predefined type map has two parameters: the first is the type of the keys, and the second is the type of the associated values.
Internally, LIGO sorts maps in increasing order by their keys. Therefore, the type of the keys must be comparable, which means that Michelson allows them to be compared. Most primitive types are comparable, including strings, ints, nats, and addresses. To create more complex keys, you can use a tuple of two comparable types. For more information about Michelson types and which types are comparable, see Michelson in the Octez reference.
Maps are appropriate for small data sets and data sets that you want to load all at once, such as if you want to run logic on every element or check their lengths. For data sets that may become larger, consider using a Big-map. Big-maps can be more efficient for larger data sets because only the elements that you access are loaded, which reduces gas fees. However, this means that contracts can't do things that require them to load the entire big-map.
Creating maps
To create a map, you can use the predefined value Map.empty or create a non-empty map by passing a list of pairs of keys and values to the function Map.literal.
This example creates a map type that uses a string for the key and a list of strings for the value:
The Map.literal predefined function builds a map from a list of key-value pairs, [<key>, <value>].
For reference, see the predefined namespace Map.
Sizing maps
The predefined function Map.size returns the number of bindings
(elements) in a given map.
Searching for elements
The predefined function Map.mem returns true if a value exists in the map for a given key.
To get the value for a key, use the Map.find_opt function, which returns an option.
If the key exists in the map, the option is Some() with the value.
If the key does not exist in the map, the option is None().
Because the return value of the Map.find_opt function is an option, you must account for missing keys in the map by matching the return value, as in this example:
As shorthand, you can use the function Map.find.
This function behaves like the previous example: it returns the value for a key if it exists or fails with the message MAP FIND if the value does not exist.
Adding elements
To add an element to a map, pass the key and value to the Map.add function.
If the key already exists, the corresponding value is updated.
Removing elements
The function Map.remove creates a map containing the elements of a given map, without the element with the given key.
If the element is not already present, the new map is the same as the old one.
Updating elements
Previous sections show how to add and remove an element from a map.
The function Map.update can do both depending whether some value is given for the new binding or not.
To update a map in this way, pass the key and an option with the value.
If the option is Some(value), the function adds the element, replacing any element with the given key.
If the option is None(), the function removes the element with the given key if it exists.
In either case, the function returns a new map, as in these examples:
To simultaneously update a map and obtain the value of the updated element, use the function Map.get_and_update.
This function allows you to extract a value from a map for use, as in this example:
Working with maps as a whole
As described earlier, you can run logic on an entire map, but not a big-map. LIGO runs logic on entire maps by applying a functional iterator to each element in the map. In JsLIGO, you can also loop through the elements in a map, but this is not possible in CameLIGO.
Folding maps
A map fold, known in some other languages as a reduce, runs the same function on each element in a map and returns a single value that is the result of those functions.
The function that you pass to the Map.fold function receives two arguments:
- The accumulator, which is the result of the previous function iteration
- The value of the current element
Each iteration of the function returns a new accumulator, which is passed to the next function.
The result of the last function iteration is the return value of the Map.fold function.
The Map.fold function iterates over the map in increasing order of its keys.
The Map.fold function accepts these parameters:
- The fold function
- The map to fold
- The starting value for the accumulator
For example, this code calculates the sum of the nats in a map. At each iteration, the accumulator is the value of the sum of the elements up to that point.
Mapping maps
The mapping operation (not to be confused with the map type itself) runs the same function on every value in a map and returns the resulting map. Unlike folding, mapping operates on each element in the map independently from the others and returns a new map.
The function that you pass to the Map.map function receives the key and value of the current element and returns the new value for the same key.
You cannot change the key with this function; the new map has the same keys as the old map.
The following example takes a map of integers and squares each integer, producing a map with the same keys and the squared values:
Iterating over maps
An iterated operation is a fold over a map that returns the value of type unit, that is, its only use is to produce side-effects.
For example, iterating over maps can be useful if you want to verify that each element in a map meets certain criteria, and fail with an error otherwise.
To iterate over a map, pass the function to apply to each element to the Map.iter function.
This example iterates over a map of integers and fails if any of them are not greater than 3:
Looping
To iterate through all of the elements in a map, in increasing order of the keys, use the for loop in the form for (const <variable> of <map>) <block>.
In this loop, the <block> of statements (or a single statement) runs once for each <variable> ranging over the elements of the map <map> in increasing order.
Here is an example that adds the values in a map: