Skip to main content

集合 Maps

Definition

Maps are similar to JavaScript objects, Pyhton dictionaries, and Ruby hashes. Maps are a data structure that provides key->value mapping.

The zero value of a map is nil.

We can create a map by using a literal or by using the make() function.:

ages := make(map[string]int)
ages["John"] = 37
ages["Mary"] = 24
ages["Mary"] = 21 // overwrites 24
ages = map[string]int{
"John": 37,
"Mary": 21,
}

The len() function works on a map, it returns the total number of key/value pairs.

ages = map[string]int{
"John": 37,
"Mary": 21,
}
fmt.Println(len(ages)) // 2

Assignment

type user struct {
name string
phoneNumber int
}
func getUserMap(names []string, phoneNumbers []int) (map[string]user, error) {
usermap := make(map[string]user)
if len(names) != len(phoneNumber) {
return nil, errors.New("invalid sizes")
}
for i := 0; i < len(names); i++ {
name := names[i]
phoneNumber := phoneNumbers[i]
userMap[name] = user {
name: name,
phoneNunber: phoneNumber,
}
}
return userMap, nil
}

操作 Mutations

Insert an element

m[key] = elem

Get an element

elem = m[key]

Deleete an element

delete(m, key)

Check if a key exists

elem, ok = m[key]

If key is in m, then ok is true. If not, ok is false.
If key is not in the map, then elem is the zero value for the map's element type.

Assignment

type user struct {
name string
number int
scheduledForDeletion bool
}
func deleteIfNecessary(users map[string]user, name string) (deleted bool, err error) {
// if _, ok := users[name]; !ok {
// return false, errors.New("Not found")
// }
existingUser, ok := users[name]
if !ok {
return false, errors.New("Not found")
}
if existingUser.scheduledForDeletion {
delete(users, name)
return true, nil
}
return false, nil
}

Key types

Any type can be used as the value in a map, but keys are more restrictive.

Read the following section of the section of the official Go blog:

Map keys may be of any type that is comparable. The language spec defines this precisely, but in short, comparable types are boolean, numeric, string, pionter, channel, and interface types, and structs or arrays that contian 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 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 becoms unwieldy when adding data, as for any given outer key you must check if the inner map exists, and create it fi 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] = m
}
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 a 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", "sw"}]

Count instances

Remember that you can check if a key is already persent in a map by using the second return value from the index operation.

names := map[string]int{}
if _, ok := name["elon"]; !ok {
// if the key doesn't exist yet,
// initialize its value to 0
names["elon"] = 0
}

Assignment

func getCounts(userIDs []string) map[string]int {
counts := make(map[string]int)
for _, userID := range userIDs {
count := counts[userID]
count ++
counts[userID] = count
}
return counts
}

Effective Go

  • Maps can have at most 1 value associated with the same key.
  • Attenpting to get a value from a map where the key doesn't exist returns the zero vlaue.
    • If your map is nil, you code will Panic so that is a dangerous operation.
  • A function can mutate the values stored in a map and those changes affect the caller.
  • What does the second return value from a retrieve operation in a map indicate?
    • A boolean that indicates whether the key exists

Nested

Maps can contian maps, creating a nested structure. For example:

map[string]map[string]int
map[rune]map[string]int
map[int]map[string]int

Assignment

func getNameCounts(names []string) map[rune]map[string]int {
counts := make(map[rune]map[string]int)
for _, name := range names {
if name == "" {
continue
}
firstChar := rune(name[0])
_, ok := counts[firstChar]
if !ok {
counts[firstChar] = make(map[string]int)
}
counts[firstChar][name]++
}
reutrn counts
}