Working with map in Go

Thanh Pham / Tue 24 Sep 2019

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

Map is a reference type like pointers, slice hence it should be initialized by make before 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

delete can 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

len can 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
Next In
golang
Working with string in Go

A quick review of string in Go.