The Go map is a composite type that is used as containers for storing unordered elements of the same type indexed by an arbitrary key value. The following code snippet shows a variety of map variables declarations with a variety of key types:
var ( legends map[int]string histogram map[string]int calibration map[float64]bool matrix map[[2][2]int]bool // map with array key type table map[string][]string // map of string slices // map (with struct key) of map of string log map[struct{name string}]map[string]string )
golang.fyi/ch07/maptypes.go
The previous code snippet shows several variables declared as maps of different types with a variety of key types. In general, map type is specified as follows:
map[<key_type>]<element_type>
The key specifies the type of a value that will be used to index the stored elements of the map. Unlike arrays and slices, map keys can be of any type, not just int
. Map keys, however, must be of types that are comparable including numeric, string, Boolean, pointers, arrays, struct, and interface types (see Chapter 4, Data Types, for discussion on comparable types).
Similar to a slice, a map manages an underlying data structure, opaque to its user, to store its values. An uninitialized map has a nil zero-value as well. Attempts to insert into an uninitialized map will result in a program panic. Unlike a slice, however, it is possible to access elements from a nil map, which will return the zero value of the element.
Like other composite types, maps may be initialized using a composite literal value of the following form:
<map_type>{<comma-separated list of key:value pairs>}
The following snippet shows variable initialization with map composite literals:
var ( histogram map[string]int = map[string]int{ "Jan":100, "Feb":445, "Mar":514, "Apr":233, "May":321, "Jun":644, "Jul":113, "Aug":734, "Sep":553, "Oct":344, "Nov":831, "Dec":312, } table = map[string][]int { "Men":[]int{32, 55, 12, 55, 42, 53}, "Women":[]int{44, 42, 23, 41, 65, 44}, } )
golang.fyi/ch07/mapinit.go
The literal mapped values are specified using a colon-separated pair of key and value as shown in the previous example. The type of each key and value pair must match that of the declared elements in the map.
Similar to a slice, a map value can also be initialized using the make function. Using the make function initializes the underlying storage allowing data to be inserted in the map as shown in the following short snippet:
func main() { hist := make(map[int]string) hist["Jan"] = 100 hist["Feb"] = 445 hist["Mar"] = 514 ... }
golang.fyi/ch07/maptypes.go
The make
function takes as argument the type of the map and it returns an initialized map. In the previous example, the make
function will initialize a map of type map[int]string
. The make
function can optionally take a second parameter to specify the capacity of the map. However, a map will continue to grow as needed ignoring the initial capacity specified.
As is done with slice and arrays, index expressions are used to access and update the elements stored in maps. To set or update a map
element, use the index expression, on the left side of an assignment, to specify the key of the element to update. The following snippet shows an element with the "Jan"
key being updated with the value 100
:
hist := make(map[int]string) hist["Jan"] = 100
Accessing an element with a given key is done with an index expression, placed on the right side of an assignment, as shown in the following example, where the value indexed with the "Mar"
key is assigned the val
variable:
val := hist["Mar"]
Earlier it was mentioned that accessing a non-existent key will return the zero-value for that element. For instance, the previous code would return 0 if the element with the key "Mar"
does not exist in the map. As you can imagine, this can be a problem. How would you know whether you are getting an actual value or the zero-value? Fortunately, Go provides a way to explicitly test for the absence of an element by returning an optional Boolean value as part of the result of an index expression, as shown in the following snippet:
func save(store map[string]int, key string, value int) { val, ok := store[key] if !ok { store[key] = value }else{ panic(fmt.Sprintf("Slot %d taken", val)) } }
golang.fyi/ch07/map_use.go
The function in the preceding snippet tests the existence of a key before updating its value. Called the comma-ok idiom, the Boolean value stored in the ok
variable is set to false when the value is not actually found. This allows the code to distinguish between the absence of a key and the zero value of the element.
The for…range
loop statement can be used to walk the content of a map value. The range
expression emits values for both key and element values with each iteration. The following code snippet shows the traversal of map hist
:
for key, val := range hist { adjVal := int(float64(val) * 0.100) fmt.Printf("%s (%d):", key, val) for i := 0; i < adjVal; i++ { fmt.Print(".") } fmt.Println() }
golang.fyi/ch07/map_use.go
Each iteration returns a key and its associated element value. Iteration order, however, is not guaranteed. The internal map iterator may traverse the map in a different order with each run of the program. In order to maintain a predictable traversal order, keep (or generate) a copy of the keys in a separate structure, such as a slice for instance. During traversal, range over the slice of keys to traverse in a predictable manner.
You should be aware that update done to the emitted value during the iteration will be lost. Instead, use an index expression, such as hist[key]
to update an element during iteration. For details on for…range
loop, refer to Chapter 3, Go Control Flow, for a thorough explanation of Go for
loops.
Besides the make
function, discussed earlier, map types support two additional functions discussed in the following table:
Function |
Description |
len(map) |
As with other composite types, the built-in h := map[int]bool{3:true, 7:false, 9:false} fmt.Println(len(h))
The |
delete(map, key) |
The built-in h := map[int]bool{3:true, 7:false, 9:false} delete(h,7) fmt.Println(len(h)) |
Because a map maintains an internal pointer to its backing storage structure, all updates to map parameter within a called function will be seen by the caller once the function returns. The following sample shows a call to the remove
function to change the content of a map. The passed variable, hist
, will reflect the change once the remove
function returns:
func main() { hist := make(map[string]int) hist["Jun"] = 644 hist["Jul"] = 113 remove(hit, "Jun") len(hist) // returns 1 } func remove(store map[string]int, key string) error { _, ok := store[key] if !ok { return fmt.Errorf("Key not found") } delete(store, key) return nil }
golang.fyi/ch07/map_use.go
18.119.133.160