Working with map in Go.

Map in Go is declared as map[KeyType]ValueType, where KeyTypecan be any type that is comparable, and ValueTypecan be any type, including another map.

Map is a reference type like pointers, slice hence it should be initialized by makebefore using. It can also be created and initialized by map literals:

m1 := make(map[string]string) // using make

m2 := map[int64]float64{} // without values
m3 := map[string]int{ // with values
"a": 1,
"b": 2,
}

// print values
fmt.Println(m1)
fmt.Println(m2)
fmt.Println(m3)

Playground

Map value can be set and retrieve by its key:

m := make(map[string]string)

// set values
m["r"] = "Rob Pike"
m["k"] = "Ken Thompson"
m["g"] = "Robert Griesemer"

// retrieve values
v := m["r"]
v1 := m["k"]

fmt.Println(v)
fmt.Println(v1)

Playground

deletecan be used to delete a key from the map:

m := map[string]int{
"a": 1,
"b": 2,
"c": 3,
}
delete(m, "a")
delete(m, "c") // delete a non-exist key is OK.

fmt.Println(m) // map[b:2]

Playground

lencan be used to calculate length of the map:

m := map[string]int{ // with values
"a": 1,
"b": 2,
}
fmt.Println("len=", len(m)) // len= 2

Playground

for-range can be used to range over the map. Note that the keys are not guarantee in the same order each time you range over the map.

m := map[string]int{
"a": 1,
"b": 2,
"c": 3,
}
for k, v := range m {
fmt.Println(k, "=", v)
}

fmt.Println("keys are not guarantee in the same order each time...")
for k, v := range m {
fmt.Println(k, "=", v)
}

Playground

If a key doesn't exist in the map, retrieving its value return zero value of the ValueType. A two-value assignment can be used for checking the existence of the key:

m := make(map[string]int)
m["r"] = 1
m["k"] = 0

gValue, gExistence := m["g"]
kValue, kExistence := m["k"]

fmt.Printf("k=%d, exist: %t\n", kValue, kExistence)
fmt.Printf("g=%d, exist: %t\n", gValue, gExistence)

Playground

zero value is very useful for some cases. Below is a program to count the number of time appear in a string:

s := `Go provides a built-in map type that implements a hash table`

wordCount := make(map[string]int)
words := strings.FieldsFunc(s, func(r rune) bool{
return r == '.' || r == ' '
})
for _, w := range words {
wordCount[w]++
}

fmt.Println(wordCount)

Playground

Any comparable type can be used as key of map. Below is an example of using struct as a key:

type country struct {
name string
code int
}
visits := make(map[country]int)
visits[country{"Vietnam", 84}] = 1000
visits[country{"USA", 0}] = 1

target := country{"USA", 0}
visitTimes, visited := visits[target]
fmt.Printf("Visited %5s: %5t, number of times: %d\n", target.name, visited, visitTimes)

target = country{"India", 0}
visitTimes, visited = visits[target]
fmt.Printf("Visited %5s: %5t, number of times: %d\n", target.name, visited, visitTimes)

Playground

map is not concurrency safe. A synchronization mechanism must be applied. sync.RWMutex can be used for synchronization.

Below is a complex example where we create 100 lines of words and start a goroutine to count number of words appear in each lines:

package main

import (
"bytes"
"fmt"
"strings"
"sync"
)

type counter struct {
sync.RWMutex
count map[string]int
}

func (c *counter) Get(w string) int {
c.RLock()
defer c.RUnlock()
return c.count[w]
}

func (c *counter) GetAll() map[string]int {
c.RLock()
defer c.RUnlock()
return c.count
}

func (c *counter) Increase(w string, t int) {
c.Lock()
defer c.Unlock()
c.count[w] += t
}

func main() {
// create 100 lines
buff := bytes.Buffer{}
for i := 0; i < 100; i++ {
for j := 0; j < 100; j++ {
buff.WriteString("a b c ") // hard-code...
}
buff.WriteByte('.')
buff.WriteByte('\n')
}
s := buff.String()

// for each lines, start a goroutine to count number of time a word appear.
lines := strings.FieldsFunc(s, func(r rune) bool {
return r == '\n'
})
counter := &counter{
count: make(map[string]int),
}
var wg sync.WaitGroup
for _, line := range lines {
wg.Add(1)
line := line
go func() {
defer wg.Done()
words := strings.FieldsFunc(line, func(r rune) bool {
return r == '.' || r == ' '
})
for _, w := range words {
counter.Increase(w, 1)
}
}()
}
wg.Wait() // make sure all goroutine has been done.
fmt.Println(counter.GetAll())
}

Playground

Read more about map in Go official blog: https://blog.golang.org/go-maps-in-action

Questions #

Explain why you cannot take address of a map element?

p := &m[key] // cause panic