Golang composite data types – Maps

Go supports composite types such as array, slice, maps and structures. We discussed about Arrays and Slices in the previous blog, now is the time to go through the concept of maps in Golang.

Golang Maps

One of the most useful data structures in computer science is the hash table. Almost every language provides a hash table implementation, that offers fast lookups, adds, and deletes. Go provides a built-in map type that implements a hash table and are suitably termed as Maps. Like in other languages where we have dictionaries (Python) or hash maps (Java), Maps are Golang’s built in associative data types where every value is associated with a key. Let us learn more about Maps in Golang in the way we best know – yes, by writing code!

Declaring and Initializing Maps

Maps in Golang can be visualized in this way => map[keyType]valueType so a map where we want to store the scores of students by their names (which has strings as keys and integers as values) would be written like scores[string]int.

A map can be initialized in two different ways:

  1. var mscores := make(map[string]int)
  2. var score map[string]int

The first option uses make(map[keyType]valueType) function available to us from Go standard library to initialize a map, but you could argue and say why can’t it be simple enough to create a map this way: var score map[string]int? Let’s look at the difference between the two.

make, allocates and initializes a hash data structure and it returns a value mscores that points to the newly created structure. But in the later case, a new hash structure is not initialized, so the value of score is nil. Map types are reference types, like pointers or slices, and so the value of m above is nil. Adding data to this map will result in runtime errors as we will see in the example.

 

As you can see in the above code snippet, there are multiple ways to initialize maps with keys and values. You can either individually assign keys with values or you could initialize all values in a single statement

Assigning individual keys:

var mscores = make(map[string]int)

mscores["Chetan"] = 90

Initialize all keys of a Map:

values := map[string]int{
       "abc": 123,
       "def": 345,
       "ghi": 567,
       "jkl": 897,
   }

Cool, now that we have the Maps ready, let’s look at all the operations we can perform with them.

Operations with Maps

Insert and Retrieve: Adding elements to maps and retrieving keys from map is very trivial and intuitive. In the below example, we can set the scores for Alice, John using their names as keys and setting the values with their scores with this code: mscores[“Alice”]  = 30

To retrieve the score for a given student, we can return the value with the index like this: var aliceScore := mscores[“Alice”]

Iterate over map elements: We could also retrieve all the keys and values in a map using the for loop using the helper function range. Note that the order of iteration will never be the same (as probably with previous versions of Go). But if your specific use case depends on maintaining orders, you could sort the map based on the keys and retrieve the elements and work on them.

Zero values: Now what happens when we try to retrieve John’s score? As we haven’t added any value for John, Golang will return the zero value for the key, so the statement mscores[“Bob”]  will print 0

Length of map: We can easily figure out the number of keys that are present in a map using the built in function len(map). For instance, if we would like to count the number of students that are stored in our students map, using len() is the way to go about it.

Existence check: More often than not, we need to check a key is part of the map we are working on. For example, if student Bob does exist in the map before we even start working on it? Golang handles this check for us.

When we try to access an element, Golang returns two values:

  1. a boolean value that indicates if the key exists in the map and if not
  2. it also returns the zero value as we saw in the Bob’s case

In our example, since the key “Bob” does not exist in mscores map, the variable ok returns false.

Deleting elements: Go also provides a built in function delete(map, key) that takes in the key and deletes it from the map. So if at run time we want to delete a student from the map, we can do using the statement: delete(mscores, “Alice”). In our code snippet, when we try to print all elements of the map mscores, the key “Alice” is removed and the only keys available are “John” and “Chetan”.

Maps as reference types

Like slices, map types in Golang are also reference types, which means if map is assigned to a new variable, then a change in map will change the values for both the variables. This is because both the variable point to the same hashmap data structure. For instance, in the below example, the new variable (newVeggies) also points to the same map (veggies), so when we change the value of orka key in the veggies map, they reflect in the newVeggies as well.

Passing maps to the functions: Maps can also be passed to the functions and yes as you guessed it, they are passed as references. In the above example, when we add more elements to the map numbers in updateNumbers() function, the map numbers is also changed in the main function.

Deep Copy

As we noticed in the previous section, maps are reference types, so the changes that happened to the map in the function got propagated over to the main function. But we can easily avoid this situation by copying the map so that the original map is always maintained. We achieve by performing a deep copy of the map.

As you can see in the above code snippet, the value of scores has not changed even when we add a new element in dstScores map. So these two maps are now independent of each other and can be worked upon.

Nested Maps

More often than not, our data is going to be more complex or involved that the examples we have seen so far. Imagine a map shoppingList that contains details of the fruits, vegetables and other necessary items that need to be bought from a local grocery shop.

Now in this example, you would need to not only store different types of items but there can be subcategories for every item type. For instance, veggies internally will have onion, orka and they need to be bought in the quantities of 2kg and 3kg respectively. How will we store the shoppingList then?

So at the first level, the shoppingList map will look like

shoppingList := make map[string]int{“veggies”:2, “fruits”:3, “other”:6}. This map will tell us we need to purchase 2 veggies and 3 different types of fruits. But to store the details of which veggies to be bought and in what quantity, we need to expand the map. We do this by nesting of maps, so the first level map will store the category of grocery items we need to purchase (like veggies and fruits) while the associated nested map contains the details for every category (like onion 2kgs and 12 bananas).

In the above example, veggies and fruits are the top level categories of our shopping list and the map inside veggies and fruits keys, store details about what items to purchase and in what quantities.

Concurrency and Maps

You must have heard or experienced that maps in Golang are not safe for concurrent use. So what does this mean?

In case where multi goroutines are trying to read from or write into maps simultaneously ( or concurrently ), there could be cases of stale data being read or in case of uncontrolled access, it can lead to runtime crash or panic. Problems typically occur during updates and not if goroutines are trying to just lookup data from maps. Essentially operations on maps are not atomic in Go and this is not great.

To prevent this problem, we will need to synchronize access to the maps which can be achieved with mutexes. Some of the possible implementations may involving using:

  1. sync.RWMutex – reader/writer mutual exclusion lock
  2. atomic.AddUint32 –  provides low-level atomic memory primitives useful for implementing synchronization algorithms.
  3. sync.Map – map that is safe for concurrent use by multiple goroutines

Hope you are confident using Maps in Golang, feel free to comment or post suggestions. Here are a few resources on maps that would be useful to you, see you all soon 🙂

Good Reads

  1. Go by example: https://gobyexample.com/maps
  2. Go blog: https://blog.golang.org/go-maps-in-action
  3. Golang.org: https://golang.org/doc/faq#atomic_maps

 

 

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.