What a hash map is
A hash map stores key-value pairs and finds a value by its key without scanning the whole collection. Reading, adding, updating, deleting and testing for a key all take O(1) time on average. Go provides the hash map as the built-in map type, written map[KeyType]ValueType. Keys must be comparable with ==, which rules out slices and maps as keys; strings, numbers, booleans and structs of such fields are fine.
Reach for a map whenever you ask "have I seen this?" or "how many times?" about many items: counting, lookups by name, remembering an index.
Creating and filling a map
An empty map is made with a literal or with make:
digitValue := map[string]int{}
other := make(map[string]int)
A map can also start with entries. When the closing brace is on its own line, every pair needs a trailing comma, the last one included:
digitValue := map[string]int{
"zero": 0,
"one": 1,
}
A map that is declared but never created, as in var m map[string]int, is nil. Reading from a nil map works and gives zero values, but writing to it panics with "assignment to entry in nil map". Always create the map with a literal or make before storing anything.
Reading, adding and updating
Square brackets read, add and update. Assigning to a new key adds an entry; assigning to an existing key replaces its value:
digitValue["three"] = 3
digitValue["zero"] = 5
digitValue["one"]++
Compound operators such as += and ++ work directly on a map entry, which is why counting is so short in Go.
Reading a missing key does not fail and does not add the key. It gives the zero value of the value type: 0 for numbers, "" for strings, false for booleans.
fmt.Println(digitValue["three"], len(digitValue))
With a map that holds only "zero" and "one", this prints:
0 2
The size is still 2, so the read added nothing.
The comma-ok idiom
The zero value creates an ambiguity: digitValue["zero"] is 0 because the key holds 0, and digitValue["three"] is also 0 because the key is missing. A map read can return a second result, a boolean that says whether the key is present:
v, ok := digitValue["zero"]
fmt.Println(v, ok)
_, ok = digitValue["three"]
fmt.Println(ok)
0 true
false
Use _ for the value when only the presence matters. The idiom is often placed in the init part of an if, where the variables exist only inside that statement: if v, ok := m[k]; ok { ... }.
Deleting
The built-in delete(m, key) removes an entry. Deleting a key that is not there does nothing, so no check is needed first. Deleting from a nil map is also safe.
Iterating
for key, val := range m visits every entry, and for key := range m visits the keys only. The order is unspecified and can differ from one run to the next, so never rely on it. When you need a stable order, keep the keys in a slice and loop over the slice, which is what the exercise does. Iteration costs O(n).
The lesson's exercise
The exercise tracks a fruit stock. The starting code already declares the slice order := []string{"apples", "bananas", "grapes"}. The steps build on it:
- Create
fruitsas amap[string]intliteral with apples 3, bananas 5 and grapes 2. - Add four apples with
fruits["apples"] += 4, which makes the count 7. - Test for pears. The comma-ok form in the
ifinit, with the value discarded, prints "Out of pears" whenokis false. - Remove bananas with
delete. - Print the stock in a stable order. Loop over
order, look each name up with comma-ok, and print only the names still present. Because the bananas are gone, they are skipped.
fruits := map[string]int{
"apples": 3,
"bananas": 5,
"grapes": 2,
}
order := []string{"apples", "bananas", "grapes"}
fruits["apples"] += 4
if _, ok := fruits["pears"]; !ok {
fmt.Println("Out of pears")
}
delete(fruits, "bananas")
for _, fruit := range order {
if count, ok := fruits[fruit]; ok {
fmt.Println("We have " + strconv.Itoa(count) + " " + fruit + ".")
}
}
This prints:
Out of pears
We have 7 apples.
We have 2 grapes.
strconv.Itoa converts the int count to a string. Go does not convert numbers to strings in a + concatenation, and string(count) would produce the character with that code point, not the digits. The program needs the fmt and strconv imports.
Common mistakes
- Writing to a nil map, which panics.
- Using the plain read to test membership when the value type's zero value is a legitimate stored value.
- Expecting insertion order from
range. - Forgetting the trailing comma in a multi-line literal, which is a syntax error.
Key takeaways
map[K]Vis Go's hash map; create it with a literal ormake.- Missing keys read as the zero value and are not added.
v, ok := m[k]tells a missing key from a stored zero.deleteis safe on missing keys;rangeorder is random.- All single-key operations are
O(1)on average.