cover image for article

Golang: Maps

Edit

Author: Meet Rajesh Gor

#go

Introduction

In the seventh part of the series, we will be covering Maps. We have covered some basic data structures like arrays and slices, and now we can move into maps or hash tables. Maps allow us to store key-value pairs of a particular type. In this part of the series, we will be covering the basics of Maps in Golang like declaration, iteration, and Creating, updating, and deleting keys from the map.

Maps in Golang

Maps in golang are data structures that provide a way to store key-value pairs. It is also known as a hash table. Maps allow us to create unique keys which are associated with a value. It can be used to create a data structure that can have an item that is associated with a particular value, for example, the basic example of the map can be a frequency table of a list of numbers. We can store the frequency of each element occurring in the list. Let's say we have a list of numbers as [3, 5, 9, 4, 9, 5, 5], we can create a map of the frequency of these elements as [3:1, 5:3, 4:1, 9:2]. Here, we have stored the information in the form of key-value pairs as a frequency. So, 3 has occurred one time, 5 3 times, and so on.

Maps are not stored in order of the numbers they are unordered so we need to manually sort them in the order we want.

Declaring Maps

We can declare maps by defining the type of mapping like the two types we are mapping. We can map any type with any other, like a character with an integer, an integer with an integer as we saw earlier, etc. We have several ways to decalre maps in golang, like using map literal, make function, new function, and a few others. We'll look into each of them in a brief.

Simple map literal

As we saw in the array and slices, we used the slice literals to declare and initialize an array or a slice. Similarly, we can use the map literal to create a map in golang. Here, we use the map keyword followed by the two types of data we are going to map with.


go
package main

import "fmt"

func main() {

	char_freq := map[string]int{
		"M": 1,
		"e": 2,
		"t": 1,
	}
	fmt.Println(char_freq)
}

$ go run map.go
map[M:1 e:2 t:1]

We have used the map keyword to initialize a map with a string with int. The first data type is declared inside the square brackets[] and the second data type outside the square brackets. We use the {} to define the map values. We can even leave the {} empty.


char_freq := map[string]int{}

We initialize the values of the map by specifying the data for that data type in this example a string "" followed by a colon : and finally the value of the second pair data. Each value is separated by a comma(,).

Using make function

We can even use the make function to create a map in golang. The make function is used for allocating memory. The make function allocates memory which might be enough for the initial values provided. It allocates more memory as the map grows in size. We use the make function by providing the map keyword along with the data types of the key values pairs to be mapped. Optionally we can provide the capacity as we provided in the slice declaration. It basically doubles once it reaches the limit and is re-allocated.


go
marks := make(map[int]int)
marks[65] = 8
marks[95] = 3
marks[80] = 5
fmt.Println(marks)

$ go run map.go
map[65:8 80:5 95:3]

We have used the make function for declaring the map, the initial size is around 7 if not mentioned. After it hits 7, the capacity is mostly doubled and increased as per the modifications.

Using the new function

We can even use the new function(a bit hacky) to crated a map in golang. The new function basically is used to allocate memory but is not the same as the make function, it returns the memory address to an allocated pointer. So, we can set the value of the returned function call of the new function with a pointer variable. A pointer in golang is simply a reference to a memory address, we'll dive into pointers in a different section. After the pointer is assigned a memory address, we can refer to the address of that pointer and thus access the original value which is the map itself.


go
name := new(map[byte]int)
*name = map[byte]int{}
name_map := *name

name_map['m'] = 1
name_map['e'] = 2
name_map['t'] = 1

fmt.Println(name_map)

for k, _ := range name_map {
    fmt.Printf("%c\n", k)
}

$ go run map.go
map[101:2 109:1 116:1]
m
e
t

So, we can see we created the map with the new function and stored the address into a pointer, later we initialized the empty map and stored the initial reference in the same pointer address. Then, we can finally store the map in another variable so that we can use it as a normal map. So, this is how we declare the map using the new function.

Access Keys and Values in Maps

We can access the values by simply accessing them with the keys. Using the square bracket and the key literal into the braces, we get the value associated with that key. For example, the map ["M": 1, "E": 2, "T":1], we can use the map_name["E"] which will get the value as 3.

Length of Map

The length of the map can be accessed using the len function, the len function returns the number of key-value pairs in the map.


go
char_freq := map[string]int{
    "M": 1,
    "e": 2,
    "t": 1,
}
fmt.Println(char_freq)
fmt.Println(len(char_freq))

$ go run map.go
map[M:1 e:2 t:1]
3

Check for existing Keys in Map

We can check if a key exists in the map by using the comma-ok syntax. The key can be accessed using the first variable and if the key doesn't exist, the second variable is set to false. So, we can verify the existence of a key in the map using the two-variable approach.


go
name_map := map[byte]int{
    'm': 1,
    'e': 2,
    't': 1,
}
var key byte = 't'
value, exist := name_map[key]
if exist == true {
    fmt.Printf("The key %c exist and has value %d\n", key, value)
} else {
    fmt.Printf("The key %c does not exist.\n", key)
}

$ go run map.go
The key t exist and has value 1

So, we can see the exist value is true if the key exists and false if it doesn't. So, we can then verify if a particular key exists in a map or not.

Adding and Modifying Keys/Values in Maps

We can add a key-value pair in a map by just using the key as we did in the initialization process. We simply pass the key in the square braces [] and assign it a value appropriate to the data type used in the map.


go
cart_list := map[string]int{
    "shirt": 2,
    "mug": 4,
    "shoes": 3,
}

fmt.Println(cart_list)

cart_list["jeans"] = 1
cart_list["mug"] = 3
fmt.Println(cart_list)

$ go run map.go
map[mug:4 shirt:2 shoes:3]
map[jeans:1 mug:3 shirt:2 shoes:3]

We can access the keys in the map by just using the key as it is and altering the value it holds, the same thing applies to the addition of the key-value pairs, we can use the key and assign the value associated with it.

Delete Keys in Maps

We can delete the key-value pairs in the map, using the delete function. We pass in the key and the map to delete the key-value pair from the map.


go
cart_list := map[string]int{
    "shirt": 2,
    "mug":   4,
    "shoes": 3,
}
fmt.Println(cart_list)

cart_list["jeans"] = 1
cart_list["mug"] = 3
delete(cart_list, "shoes")

fmt.Println(cart_list)

$ go run map.go
map[mug:4 shirt:2 shoes:3]
map[jeans:1 mug:3 shirt:2]

So, we can see the key-value pair was deleted from the map.

Iterate over a Map

We can iterate over a map similar to the range keyword iteration for slices and arrays, but the exception here, is that we use the key, value instead of the index, copy of an element in the map as the range.


go
is_prime := map[int]bool{
    7:  true,
    9:  false,
    13: true,
    15: false,
    16: false,
}

for key, value := range is_prime {
    fmt.printf("%d -> %t\n", key, value)
}

$ go run map.go
9 -> false
13 -> true
15 -> false
16 -> false
7 -> true

So, we can observe that we can access the keys and values in the map using the range keyword for iterating over the map. Inside the for loop, we can refer to the assigned values present in the map.

Use only key or value while iterating

If we don't use either of the variables like key or value, the compiler might give us the unused variable error, so we have an alternative to use don't care variables namely the _ underscore character.


go
is_prime := map[int]bool{
    7:  true,
    9:  false,
    13: true,
    15: false,
    16: false,
}

for key, _ := range is_prime {
    fmt.Printf("Key : %d\n", key)
}

for _, value := range is_prime {
    fmt.Printf("Value: %t\n", value)
}

$ go run map.go
Key : 7
Key : 9
Key : 13
Key : 15
Key : 16
Value: true
Value: false
Value: true
Value: false
Value: false

So, we use the _ to ignore the usage of the variable in the loop, if we are not sure of using any variable, we can ignore it completely with the underscore operator and thus prevent any compilation errors/warnings. So, here if we want to only access keys, we use key, _ in order to fetch only keys and silence the values in the map. If we want to access only the values, we can use _, value so as to get all the values from the map. The variable name key or value can be anything but make sure to use those only inside the loop.

That's it from this part. Reference for all the code examples and commands can be found in the 100 days of Golang GitHub repository.

Conclusion

So, from this part of the series, we were able to understand the basics of maps in golang. We covered some basics stuff including the declaration, initialization, and iteration. Maps are quite simple but important for creating interesting applications.

Thank you for reading. If you have any questions or feedback, please let me know in the comments or on social handles. Happy Coding :)