| # Go maps in action |
| 6 Feb 2013 |
| Tags: map, technical |
| Summary: How and when to use Go maps. |
| OldURL: /go-maps-in-action |
| |
| Andrew Gerrand |
| |
| ## Introduction |
| |
| One of the most useful data structures in computer science is the hash table. |
| Many hash table implementations exist with varying properties, |
| but in general they offer fast lookups, adds, and deletes. |
| Go provides a built-in map type that implements a hash table. |
| |
| ## Declaration and initialization |
| |
| A Go map type looks like this: |
| |
| map[KeyType]ValueType |
| |
| where `KeyType` may be any type that is [comparable](https://golang.org/ref/spec#Comparison_operators) |
| (more on this later), |
| and `ValueType` may be any type at all, including another map! |
| |
| This variable `m` is a map of string keys to int values: |
| |
| var m map[string]int |
| |
| Map types are reference types, like pointers or slices, |
| and so the value of `m` above is `nil`; |
| it doesn't point to an initialized map. |
| A nil map behaves like an empty map when reading, |
| but attempts to write to a nil map will cause a runtime panic; don't do that. |
| To initialize a map, use the built in `make` function: |
| |
| m = make(map[string]int) |
| |
| The `make` function allocates and initializes a hash map data structure |
| and returns a map value that points to it. |
| The specifics of that data structure are an implementation detail of the |
| runtime and are not specified by the language itself. |
| In this article we will focus on the _use_ of maps, |
| not their implementation. |
| |
| ## Working with maps |
| |
| Go provides a familiar syntax for working with maps. This statement sets the key `"route"` to the value `66`: |
| |
| m["route"] = 66 |
| |
| This statement retrieves the value stored under the key `"route"` and assigns it to a new variable i: |
| |
| i := m["route"] |
| |
| If the requested key doesn't exist, we get the value type's _zero value_. |
| In this case the value type is `int`, so the zero value is `0`: |
| |
| j := m["root"] |
| // j == 0 |
| |
| The built in `len` function returns on the number of items in a map: |
| |
| n := len(m) |
| |
| The built in `delete` function removes an entry from the map: |
| |
| delete(m, "route") |
| |
| The `delete` function doesn't return anything, and will do nothing if the specified key doesn't exist. |
| |
| A two-value assignment tests for the existence of a key: |
| |
| i, ok := m["route"] |
| |
| In this statement, the first value (`i`) is assigned the value stored under the key `"route"`. |
| If that key doesn't exist, `i` is the value type's zero value (`0`). |
| The second value (`ok`) is a `bool` that is `true` if the key exists in |
| the map, and `false` if not. |
| |
| To test for a key without retrieving the value, use an underscore in place of the first value: |
| |
| _, ok := m["route"] |
| |
| To iterate over the contents of a map, use the `range` keyword: |
| |
| for key, value := range m { |
| fmt.Println("Key:", key, "Value:", value) |
| } |
| |
| To initialize a map with some data, use a map literal: |
| |
| commits := map[string]int{ |
| "rsc": 3711, |
| "r": 2138, |
| "gri": 1908, |
| "adg": 912, |
| } |
| |
| The same syntax may be used to initialize an empty map, which is functionally identical to using the `make` function: |
| |
| m = map[string]int{} |
| |
| ## Exploiting zero values |
| |
| It can be convenient that a map retrieval yields a zero value when the key is not present. |
| |
| For instance, a map of boolean values can be used as a set-like data structure |
| (recall that the zero value for the boolean type is false). |
| This example traverses a linked list of `Nodes` and prints their values. |
| It uses a map of `Node` pointers to detect cycles in the list. |
| |
| .code maps/list.go /START/,/END/ |
| |
| The expression `visited[n]` is `true` if `n` has been visited, |
| or `false` if `n` is not present. |
| There's no need to use the two-value form to test for the presence of `n` in the map; |
| the zero value default does it for us. |
| |
| Another instance of helpful zero values is a map of slices. |
| Appending to a nil slice just allocates a new slice, |
| so it's a one-liner to append a value to a map of slices; |
| there's no need to check if the key exists. |
| In the following example, the slice people is populated with `Person` values. |
| Each `Person` has a `Name` and a slice of Likes. |
| The example creates a map to associate each like with a slice of people that like it. |
| |
| .code maps/people.go /START1/,/END1/ |
| |
| To print a list of people who like cheese: |
| |
| .code maps/people.go /START2/,/END2/ |
| |
| To print the number of people who like bacon: |
| |
| .code maps/people.go /bacon/ |
| |
| Note that since both range and len treat a nil slice as a zero-length slice, |
| these last two examples will work even if nobody likes cheese or bacon (however |
| unlikely that may be). |
| |
| ## Key types |
| |
| As mentioned earlier, map keys may be of any type that is comparable. |
| The [language spec](https://golang.org/ref/spec#Comparison_operators) |
| defines this precisely, |
| but in short, comparable types are boolean, |
| numeric, string, pointer, channel, and interface types, |
| and structs or arrays that contain only those types. |
| Notably absent from the list are slices, maps, and functions; |
| these types cannot be compared using `==`, |
| and may not be used as map keys. |
| |
| It's obvious that strings, ints, and other basic types should be available as map keys, |
| but perhaps unexpected are struct keys. |
| Struct can be used to key data by multiple dimensions. |
| For example, this map of maps could be used to tally web page hits by country: |
| |
| hits := make(map[string]map[string]int) |
| |
| This is map of string to (map of `string` to `int`). |
| Each key of the outer map is the path to a web page with its own inner map. |
| Each inner map key is a two-letter country code. |
| This expression retrieves the number of times an Australian has loaded the documentation page: |
| |
| n := hits["/doc/"]["au"] |
| |
| Unfortunately, this approach becomes unwieldy when adding data, |
| as for any given outer key you must check if the inner map exists, |
| and create it if needed: |
| |
| func add(m map[string]map[string]int, path, country string) { |
| mm, ok := m[path] |
| if !ok { |
| mm = make(map[string]int) |
| m[path] = mm |
| } |
| mm[country]++ |
| } |
| add(hits, "/doc/", "au") |
| |
| On the other hand, a design that uses a single map with a struct key does away with all that complexity: |
| |
| type Key struct { |
| Path, Country string |
| } |
| hits := make(map[Key]int) |
| |
| When an Vietnamese person visits the home page, |
| incrementing (and possibly creating) the appropriate counter is a one-liner: |
| |
| hits[Key{"/", "vn"}]++ |
| |
| And it's similarly straightforward to see how many Swiss people have read the spec: |
| |
| n := hits[Key{"/ref/spec", "ch"}] |
| |
| ## Concurrency |
| |
| [Maps are not safe for concurrent use](https://golang.org/doc/faq#atomic_maps): |
| it's not defined what happens when you read and write to them simultaneously. |
| If you need to read from and write to a map from concurrently executing goroutines, |
| the accesses must be mediated by some kind of synchronization mechanism. |
| One common way to protect maps is with [sync.RWMutex](https://golang.org/pkg/sync/#RWMutex). |
| |
| This statement declares a `counter` variable that is an anonymous struct |
| containing a map and an embedded `sync.RWMutex`. |
| |
| var counter = struct{ |
| sync.RWMutex |
| m map[string]int |
| }{m: make(map[string]int)} |
| |
| To read from the counter, take the read lock: |
| |
| counter.RLock() |
| n := counter.m["some_key"] |
| counter.RUnlock() |
| fmt.Println("some_key:", n) |
| |
| To write to the counter, take the write lock: |
| |
| counter.Lock() |
| counter.m["some_key"]++ |
| counter.Unlock() |
| |
| ## Iteration order |
| |
| When iterating over a map with a range loop, |
| the iteration order is not specified and is not guaranteed to be the same |
| from one iteration to the next. |
| If you require a stable iteration order you must maintain a separate data structure that specifies that order. |
| This example uses a separate sorted slice of keys to print a `map[int]string` in key order: |
| |
| import "sort" |
| |
| var m map[int]string |
| var keys []int |
| for k := range m { |
| keys = append(keys, k) |
| } |
| sort.Ints(keys) |
| for _, k := range keys { |
| fmt.Println("Key:", k, "Value:", m[k]) |
| } |