Урок 6. Map — словари в Go
Срез хранит значения по номерам: 0, 1, 2… Часто удобнее находить значение по имени: цену — по названию товара, телефон — по фамилии. Для этого есть map (в других языках — словарь, ассоциативный массив, хеш-таблица): набор пар «ключ — значение», где каждый ключ встречается один раз.
Создание и основные действия
Тип map записывается как map[ключ]значение: map[string]int — ключи-строки, значения-числа.
package main
import "fmt"
func main() {
ages := map[string]int{"Анна": 20, "Борис": 31}
ages["Вера"] = 25
ages["Анна"] = 21
fmt.Println(ages["Анна"], len(ages))
delete(ages, "Борис")
fmt.Println(ages)
fmt.Println(ages["Глеб"])
}21 3
map[Анна:21 Вера:25]
0m[ключ] = значениедобавляет пару, а если ключ уже есть — заменяет значение.m[ключ]возвращает значение. Для отсутствующего ключа — нулевое значение типа:0,"",false. Ошибки нет.delete(m, ключ)удаляет пару; удалять отсутствующий ключ можно, ничего не случится.len(m)— число пар.
fmt.Println печатает map с ключами по порядку — так задумано в пакете fmt, чтобы вывод был одинаковым. А вот при переборе в цикле порядок другой — об этом ниже.
Есть ли ключ: v, ok
Раз для отсутствующего ключа возвращается ноль, по одному значению не понять: ключа нет или там правда 0. Для проверки берут два значения:
package main
import "fmt"
func main() {
stock := map[string]int{"яблоки": 0, "груши": 12}
if n, ok := stock["яблоки"]; ok {
fmt.Println("яблоки есть в списке, осталось", n)
}
if _, ok := stock["сливы"]; !ok {
fmt.Println("слив нет в списке")
}
}яблоки есть в списке, осталось 0
слив нет в спискеВторое значение, ok, — true, если ключ есть. Запись if v, ok := m[k]; ok встречается в программах на Go постоянно.
make и пустая map
Пустую map создают через make или пустым литералом {}:
counts := make(map[string]int)
prices := map[string]float64{}Просто объявить map нельзя: var m map[string]int даёт nil-map — читать из неё можно, а запись m["a"] = 1 вызывает панику assignment to entry in nil map. Подробно — в справке panic: assignment to entry in nil map.
Перебор и порядок ключей
range по map выдаёт пары «ключ — значение»:
for name, age := range ages {
fmt.Println(name, age)
}Порядок перебора не определён и меняется от запуска к запуску — Go перемешивает его нарочно, чтобы программы не полагались на случайный порядок. Если нужен порядок, ключи собирают в срез и сортируют.
Пример: подсчёт слов
package main
import (
"fmt"
"slices"
"strings"
)
func main() {
text := "кот пёс кот рыба пёс кот"
counts := make(map[string]int)
for _, w := range strings.Fields(text) {
counts[w]++
}
keys := make([]string, 0, len(counts))
for k := range counts {
keys = append(keys, k)
}
slices.Sort(keys)
for _, k := range keys {
fmt.Println(k, counts[k])
}
}кот 3
пёс 2
рыба 1counts[w]++ работает и для нового слова: значение отсутствующего ключа — 0, после ++ становится 1. Это обычный приём для подсчёта. for k := range counts берёт только ключи. Отсортированные ключи дают одинаковый вывод при каждом запуске.
Значения посложнее
Значением может быть что угодно: срез, структура, другая map.
package main
import "fmt"
func main() {
groups := make(map[string][]string)
groups["Минск"] = append(groups["Минск"], "Анна")
groups["Минск"] = append(groups["Минск"], "Борис")
groups["Брест"] = append(groups["Брест"], "Вера")
fmt.Println(groups["Минск"], len(groups["Минск"]))
fmt.Println(groups["Гомель"] == nil)
}[Анна Борис] 2
trueДля отсутствующего ключа groups["Гомель"] возвращает nil-срез, а append к nil-срезу работает — поэтому отдельная проверка «есть ли уже такой город» не нужна.
Какие типы бывают ключами
Ключом может быть значение, которое сравнивается через ==: числа, строки, bool, массивы, структуры из таких полей. Срез, map и функция ключом быть не могут — программа не соберётся (invalid map key type).
Map передаётся по ссылке
Map, как и срез, при передаче в функцию не копируется: функция, которая меняет map, меняет ту же самую. Копию делают вручную в цикле или функцией maps.Clone из пакета maps.
Одну map нельзя менять из нескольких горутин одновременно без защиты: программа может упасть с ошибкой concurrent map writes. Как защищать общие данные — в уроке Горутины и каналы.
Коротко
map[K]V— пары «ключ — значение»; создание — литерал{…}илиmake(map[K]V).m[k] = v— записать,m[k]— прочитать (нет ключа — нулевое значение),delete(m, k)— удалить.v, ok := m[k]— узнать, есть ли ключ.- Порядок перебора
rangeслучайный; для порядка — собрать ключи в срез и отсортировать. var m map[K]Vбезmake— nil-map, запись в неё вызывает панику.