Maps
A slice finds elements by index: 0, 1, 2. Often you want to find them by name instead: what apples cost, what Sam scored. That's what a map is for: each key leads to a value.
1package main2 3import "fmt"4 5func main() {6 prices := map[string]int{7 "apples": 5,8 "milk": 12,9 "bread": 8,10 }11 fmt.Println(prices["milk"])12 fmt.Println(prices)13}
12 map[apples:5 bread:8 milk:12]
map[string]int is "a map with string keys and int values". Inside the curly braces each entry is key: value, and every line ends with a comma (the last one too). prices["milk"] gets the value for a key.
When fmt.Println shows a whole map, it writes map[key:value …] with the keys in sorted order.
Note: Strings are sorted by their character codes, so "Zoe" comes before "adam": capital letters come first.
Store, change and len
m[key] = value stores an entry: if the key is new it is added, and if it's already there its value is replaced. len(m) is the number of entries. Build an empty map with make:
1package main2 3import "fmt"4 5func main() {6 stock := make(map[string]int)7 stock["pencils"] = 408 stock["erasers"] = 159 fmt.Println(stock, len(stock))10 11 stock["pencils"] = 3512 stock["erasers"] += 1013 fmt.Println(stock, len(stock))14}
map[erasers:15 pencils:40] 2 map[erasers:25 pencils:35] 2
A key has only one entry, so after changing "pencils" there are still 2.
Reading a missing key
Reading a key that isn't in the map is not an error: you get the zero value of the value type, 0 for int and "" for string.
What does this show?
1package main2 3import "fmt"4 5func main() {6 votes := map[string]int{}7 votes["red"]++8 votes["blue"]++9 votes["red"]++10 fmt.Println(votes["red"], votes["green"], len(votes))11}
The first time, votes["red"]++ finds no "red", reads the zero value 0, adds 1 and stores it. So "red" is 2 and "blue" is 1. Reading votes["green"] gives 0, but only reads: it doesn't add "green" to the map, so there are 2 entries.
But 0 could also be a real stored 0. To tell "stored 0" from "not there at all", read into two variables:
1package main2 3import "fmt"4 5func main() {6 stock := map[string]int{"pencils": 40, "rulers": 0}7 8 n, ok := stock["rulers"]9 fmt.Println(n, ok)10 n, ok = stock["glue"]11 fmt.Println(n, ok)12 13 if n, ok := stock["pencils"]; ok {14 fmt.Println("Pencils left:", n)15 }16}
0 true 0 false Pencils left: 40
ok is true when the key is in the map. The last form is very common: an if with a short statement, using n inside the if.
delete removes an entry
delete(m, key) removes an entry. Deleting a key that isn't there is fine too:
1package main2 3import "fmt"4 5func main() {6 cart := map[string]int{"apples": 3, "milk": 1, "bread": 2}7 delete(cart, "milk")8 delete(cart, "eggs")9 fmt.Println(cart, len(cart))10}
map[apples:3 bread:2] 2
The order is not fixed
for k, v := range m goes through every entry. But a map keeps no order: in real Go the order can be different every run (Go shuffles it on purpose, so nobody depends on it). This site uses a fixed order to make stepping easier; don't count on it.
When the result doesn't depend on the order, such as a total, looping directly is fine. To show entries in order, put the keys in a slice, sort it, then read the values in that order:
1package main2 3import (4 "fmt"5 "slices"6)7 8func main() {9 scores := map[string]int{"Sam": 88, "Mia": 92, "Dave": 75}10 11 total := 012 for _, v := range scores {13 total += v14 }15 fmt.Println("Total:", total)16 17 var names []string18 for name := range scores {19 names = append(names, name)20 }21 slices.Sort(names)22 for _, name := range names {23 fmt.Println(name, scores[name])24 }25}
Total: 255 Dave 75 Mia 92 Sam 88
for name := range scores takes just the keys. slices.Sort sorts the slice from smallest to largest; it needs import "slices".
Writing to a nil map panics
A map you only declare is nil. Reading it is fine (you get zero values), but writing to it panics:
1package main2 3import "fmt"4 5func main() {6 var counts map[string]int7 fmt.Println(counts["a"], len(counts))8 counts["a"] = 19 fmt.Println(counts)10}
0 0
panic: assignment to entry in nil map
goroutine 1 [running]:
main.main(...)
/tmp/sandbox/prog.go:8 +0x1d
exit status 2What happened: This map is nil: make it with make(map[…]…) or a literal before you put anything in it.
assignment to entry in nil map: you stored something in a nil map. var counts map[string]int is only a name, with no map behind it yet. Build one with make(map[string]int) or map[string]int{} before you store.
Counting words
Using a map as a counter is one of its most common jobs: the key is a word, and the value is how many times it appeared.
1package main2 3import "fmt"4 5func main() {6 words := []string{"apple", "banana", "apple", "orange", "apple", "banana"}7 counts := make(map[string]int)8 for _, w := range words {9 counts[w]++10 }11 fmt.Println(counts)12 fmt.Println("apple appears", counts["apple"], "times")13}
map[apple:3 banana:2 orange:1] apple appears 3 times
counts[w]++ doesn't need to check whether the key exists: a missing key reads as 0, and adding 1 is just right. Press Step and watch the entries appear and the counts go up. Lesson 9 shows how to split a sentence into words.
Practice
Write the code, then press Check. A hint costs nothing; the answer is there when you need it.
Check the stock
✓Finish check: if the item is in stock, return in stock; if not, return no such item. Note that "rulers" is stored as 0, but it is in the map, so it gets in stock (this one only asks whether the key is there).
in stock in stock no such item
1package main2 3import "fmt"4 5func check(stock map[string]int, item string) string {6 return ""7}8 9func main() {10 stock := map[string]int{"pencils": 40, "rulers": 0}11 fmt.Println(check(stock, "pencils"))12 fmt.Println(check(stock, "rulers"))13 fmt.Println(check(stock, "glue"))14}
Tally the votes
✓Finish tally: count how many votes each name got and return a map. main counts two sets of votes and should show the two lines below.
map[Ann:3 Bo:1 Cy:1] map[Bo:2]
1package main2 3import "fmt"4 5func tally(votes []string) map[string]int {6 return nil7}8 9func main() {10 fmt.Println(tally([]string{"Ann", "Bo", "Ann", "Cy", "Ann"}))11 fmt.Println(tally([]string{"Bo", "Bo"}))12}
Fix it: writing to a nil map
✓addAll records how many of each item there are, but the program panics as soon as it runs. Fix it so it shows the two lines below.
map[bread:1 milk:2] map[eggs:1]
1package main2 3import "fmt"4 5func addAll(items []string) map[string]int {6 var cart map[string]int7 for _, it := range items {8 cart[it]++9 }10 return cart11}12 13func main() {14 fmt.Println(addAll([]string{"milk", "bread", "milk"}))15 fmt.Println(addAll([]string{"eggs"}))16}
What you learned
- Build a map with
map[string]int{…}ormake(map[string]int);m[k] = vstores or changes,m[k]reads, andlen(m)counts entries. - A missing key reads as the zero value; in
v, ok := m[k],oksays whether the key is there. delete(m, k)removes an entry.- In real Go, the order of a loop over a map changes from run to run: to show entries in order, put the keys in a slice and
slices.Sortit.fmt.Println(m)sorts the keys for you. - A map you only declare is
nil: reading works, but writing panics withassignment to entry in nil map. counts[w]++counts things without checking whether the key exists.