Maps (part 1)

We have already covered two of the collections used in ClojureScript: lists and vectors. Now let us consider maps. A map is a collection that associates keys with values. Let’s say you wanted to write a program to quiz people about the Canadian provinces and their capitals. You could set up two vectors:

(def provinces ["Alberta", "British Columbia", "Manitoba", "New Brunswick",
                "Newfoundland and Labrador", "Nova Scotia", "Ontario",
                "Prince Edward Island", "Quebec", "Saskatchewan",
                "Yukon", "Nunavut", "Northwest Territories"])
(def capitals ["Edmonton", "Victoria", "Winnipeg", "Fredericton",
               "St. John’s", "Halifax", "Toronto",
               "Charlottetown", "Quebec City", "Regina",
               "Whitehorse", "Iqaluit", "Yellowknife"])

There is nothing wrong with this, but it’s inconvenient. If you let a user enter a province, you must first find its position in the provinces vector and then look up the corresponding entry in the capitals vector by its index number; thus, if you determined that Nova Scotia was at position 5 in the provinces vector (positions start at zero), then you could (get capitals 5). It would be nice if you could have a collection where the province acted as the index (also called the key) and the capital were the value. The map collection is exactly that. You define a map as key-and-value pairs in braces:

Now let’s see what it evaluates to:

Things to note:

Important

Do not confuse maps, which are a type of collection, with the map function. That they share a name is unfortunate, but we are stuck with it.

You can put the following examples into the preceding active code to see them at work. To get an entry’s value, you use the get function. For example, to get Nunavut’s capital, you could say:

(get capital-map "Nunavut")

As with lists and vectors, maps are immutable. Any operation on a map leaves the original map untouched. You can use conj to create a map with a new entry. If Canada were to absorb the fictional country of Latveria as a new province, you could give the new province and capital as a vector:

(def new-capitals (conj capital-map ["Latveria" "Doomstadt"]))

More often, however, you use the assoc function to create a new map by adding in an arbitrary number of key and value pairs. For example, if Canada were to annex both Ruritania and Graustark, you could say:

(def more-capitals (assoc new-capitals "Ruritania" "Strelsau", "Graustark" "Edelweiss"))

If one of the keys in the assoc arguments already exists in the map, its value will replace the existing value.

You can eliminate key and value pairs from a map by using dissoc, which is followed by a map and the keys you want to remove. The following will get return a new map without the key and value for Latveria and Graustark:

(dissoc more-capitals "Latveria" "Graustark")

If you give a key that doesn’t exist in the original map, it will be ignored; it won’t cause an error:

(dissoc more-capitals "Ruritania" "Uqbar") ; gets rid of just Ruritania

Iterating through Maps

You can use both map, reduce, and filter to process a map. (Yes, there’s that unfortunate overlap of terms again). The function that processes each item will receive a vector consisting of the key and value. Here is an application of map that will yield a sequence of the province names converted to upper case:

And here’s some code that uses reduce to create a new map where the capitals are the key and the provinces are the value. Notice the use of destructuring in the reduction function, which uses the value as key and vice versa:

You can also get the keys and values as separate sequences with the keys and vals functions:

Next Section - Maps (part 2)