What is a map
?
A map is like an array
except, instead of an integer index
, you can have string
or any other data types as a key
. An illustration of a map looks like
{
stringKey: intValue,
stringKey: intValue
...
}
The syntax to define a map is
var myMap map[keyType]valueType
Where keyType
is the data type of map keys while valueType
is the data type of map values. A map
is a composite data type because it is composed of primitive data types (see variables lesson for primitive data types).
Let’s declare a simple map.
package main
import "fmt"
func main() {
var m map[string]int
fmt.Println(m)
fmt.Println("m == nil", m == nil)
}
// map[]
// m == nil true
In the above program, we have declared a map m
which is empty, because zero-value
of a map is nil
. But the thing about nil
map is, we can’t add values to it because like slices, map
cannot hold any data, rather they reference the internal data structure that holds the data.
So, in the case of nil
map, the internal data structure is missing and assigning any data to it will case runtime panic panic: assignment to entry in nil map
. You can use nil
map as a variable to store another non nil
map.
Creating an empty map
An empty map
is like an empty slice
with internal data structure defined so that we can use it to store some data. Like slice
, we can use make
function to create an empty map
.
m := make(map[keyType]valueType)
Let’s create a simple map age
in which we can save the age of people.
package main
import "fmt"
func main() {
age := make(map[string]int)
age["mina"] = 28
age["john"] = 32
age["mike"] = 55
fmt.Println("age of john", age["john"])
}
// age of john 32
In the above program, we have created an empty map age
which holds int
data and referenced by string
keys. You can access or assign a value of a map using a key like map[key]
.
Using that information, we have assigned some age data of mina
, john
and mike
. You can add as many values as you want, as map
like slice
can hold a variable number of elements.
Initializing a map
Instead of creating empty
map and assigning new data, we can create a map with some initial data, like array
and slice
.
package main
import "fmt"
func main() {
age := map[string]int{
"mina": 28,
"john": 32,
"mike": 55,
}
fmt.Println(age)
}
// map[john:32 mike:55 mina:28]
Accessing map data
In case array
or slice
, when you are trying to access out of index element (when the index does not exist), Go will throw an error. But not in case of map
.
When you are trying to access the value by the key
which is not present in the map, Go will not throw an error, instead, it will return zero
value of valueType
.
package main
import "fmt"
func main() {
age := map[string]int{
"mina": 28,
"john": 32,
"mike": 55,
}
fmt.Println(age["mina"])
fmt.Println(age["jessy"])
}
// 28
// 0
28
is correct because that’s the age of mina
but since jessy
is not in the map, Go will return 0
as zero-value
of data type int
is 0
.
So, to check if a key exists in the map or not, Go provide another syntax that returns 2 values.
value, ok := m[key]
Let’s see the above example in a new syntax
package main
import "fmt"
func main() {
age := map[string]int{
"mina": 28,
"john": 32,
"mike": 55,
}
minaAge, minaOk := age["mina"]
jessyAge, jessyOk := age["jessy"]
fmt.Println(minaAge, minaOk)
fmt.Println(jessyAge, jessyOk)
}
// 28 true
// 0 false
So, we are getting extra information on whether a key exists or not. If the key exists, the second parameter will be true
else it will be false
.
Length of map
We can find out how many elements contained in a map
using len
function, which we saw in array
and slice
.
package main
import "fmt"
func main() {
age := map[string]int{
"mina": 28,
"john": 32,
"mike": 55,
}
fmt.Println("len(age) =", len(age))
}
// len(age) = 3
💡 There is nothing like capacity in
map
because Go completely takes control of internal data structure ofmap
. Hence don’t try to usecap
function onmap
.
Delete map
element
Unlike slice
where you need to use a hack to delete an element, Go provide easier function delete
to delete map
element. Syntax of a delete
function is as following.
func delete(m map[Type]Type1, key Type)
delete
function demands the first argument to be a map
and the second argument to be a key
value.
package main
import "fmt"
func main() {
age := map[string]int{
"mina": 28,
"john": 32,
"mike": 55,
}
delete(age, "john")
delete(age, "jessy")
fmt.Println(age)
}
// map[mike:55 mina:28]
💡 If the key does not exist in the map, like
jessy
in above example, Go will not throw an error while executingdelete
function.
Maps comparison
Like slice
, a map can be only compared with nil
. If you are thinking to iterate over a map and match each element, you are in grave trouble. But if you are in dire need to compare two maps, use reflect
package’s DeepEqual
function (https://golang.org/pkg/reflect/).
Map iteration
Since there are no index values in map
, you can’t use simple for
loop with incrementing index
value until it hits the end. You need to use for range
to do it.
package main
import "fmt"
func main() {
age := map[string]int{
"mina": 28,
"john": 32,
"mike": 55,
}
for key, value := range age {
fmt.Println(key, "=>", value)
}
}
// john => 32
// mike => 55
// mina => 28
range
in for
loop will return key
and value
of map element. You can also use _
(blank identifier) to ignore either key
or value
in case you don’t need it, just like array
and slice
.
💡 Order of retrieval of elements in map is random when used in
for
iteration. Hence there is no guarantee that each time, they will be in order. That also explains why we can’t compare two maps.
Map
with other data types
It’s not necessary that only string
types should be the keys of a map
. All comparable types such as boolean, integer, float, complex, string, etc. can also be keys. This should be very obvious, but boolean
just blows my mind because boolean
can only represent 2 values, either true
or false
. Let’s see what happens where we can use it.
package main
import "fmt"
func main() {
age := map[bool]string{
true: "YES",
false: "NO",
}
for key, value := range age {
fmt.Println(key, "=>", value)
}
}
// true => YES
// false => NO
I guess we found ourselves a use case for boolean
key values. But what if we add duplicate keys.
package main
import "fmt"
func main() {
age := map[bool]string{
true: "YES",
false: "NO",
true: "YEAH",
}
for key, value := range age {
fmt.Println(key, "=>", value)
}
}
// ./prog.go:9:3: duplicate key true in map literal
This also proves that we are not allowed to add duplicate keys in a map.
Maps are referenced type
Like slice
, map references an internal data structure. When you copy a map to a new map, the internal data structure is not copied, just referenced.
package main
import "fmt"
func main() {
var ages map[string]int
age := map[string]int{
"mina": 28,
"john": 32,
"mike": 55,
}
ages = age
delete(ages, "john")
fmt.Println("age", age)
fmt.Println("ages", ages)
}
// age map[mike:55 mina:28]
// ages map[mike:55 mina:28]
As expected, ages
map now has two elements because we deleted one element. Not only that, but we got the same change in age
map as well. This proves that, like slice
(but unlike an array
), when you assign a variable with another map variable, they share the same internal structure.
To copy a map, you need to use for
loop.
package main
import "fmt"
func main() {
ages := make(map[string]int)
age := map[string]int{
"mina": 28,
"john": 32,
"mike": 55,
}
for key, value := range age {
ages[key] = value
}
delete(ages, "john")
fmt.Println("age", age)
fmt.Println("ages", ages)
}
// age map[john:32 mike:55 mina:28]
// ages map[mike:55 mina:28]
In the above case, we haven’t copied the map but used map keys and values to store in a different map that implements its own underlying data structure.
💡 Since
map
references internal data structure,map
passed as function parameter share same internal data structure just likeslice
. Hence, make sure to follow same guidelines as explained in the slices lesson.