aboutsummaryrefslogtreecommitdiff
path: root/content/maps.article
diff options
context:
space:
mode:
authorRuss Cox <rsc@golang.org>2020-03-15 15:50:36 -0400
committerRuss Cox <rsc@golang.org>2020-03-17 20:58:46 +0000
commit972d42d925e6cae3f8eebd9b21d445e06c2eb386 (patch)
tree737af27f0d49318b612efec874b1d1328c699d1a /content/maps.article
parentfaf1e2da2d911edc717993e8edb24fe88f99b2b5 (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.article245
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])
+ }