diff options
author | Russ Cox <rsc@golang.org> | 2020-03-15 15:50:36 -0400 |
---|---|---|
committer | Russ Cox <rsc@golang.org> | 2020-03-17 20:58:46 +0000 |
commit | 972d42d925e6cae3f8eebd9b21d445e06c2eb386 (patch) | |
tree | 737af27f0d49318b612efec874b1d1328c699d1a /content/maps.article | |
parent | faf1e2da2d911edc717993e8edb24fe88f99b2b5 (diff) |
content: rename articles to reinforce convention of short URLs
The Go blog started out on Blogger
(http://web.archive.org/web/20100325005843/http://blog.golang.org/).
Later, we moved to the current self-hosted blog server
with extra Go-specific functionality like playground snippets.
The old Blogger posts have very long URLs that Blogger chose
for us, such as "go-programming-language-turns-two" or
"two-go-talks-lexical-scanning-in-go-and", predating
the convention of giving posts shorter, more share-friendly,
typeable names.
The conversion of the old Blogger posts also predated
the convention of putting supporting files in a subdirectory.
The result is that although we've established new conventions,
you wouldn't know by listing the directory - the old Blogger
content presents a conflicting picture.
This commit renames the posts with very long names
to have shorter, more share-friendly names, and it moves
all supporting files to subdirectories. It also adds a README
documenting the conventions.
For example, blog.golang.org/go-programming-language-turns-two
is now blog.golang.org/2years, matching our more recent birthday
post URLs, and its supporting files are moved to the new 2years/ directory.
The old URLs redirect to the new ones.
Change-Id: I9f46a790c2c8fab8459aeda73d4e3d2efc86d88f
Reviewed-on: https://go-review.googlesource.com/c/blog/+/223599
Run-TryBot: Russ Cox <rsc@golang.org>
Reviewed-by: Andrew Bonventre <andybons@golang.org>
Diffstat (limited to 'content/maps.article')
-rw-r--r-- | content/maps.article | 245 |
1 files changed, 245 insertions, 0 deletions
diff --git a/content/maps.article b/content/maps.article new file mode 100644 index 0000000..e7b054e --- /dev/null +++ b/content/maps.article @@ -0,0 +1,245 @@ +# 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]) + } |