Go: с нуля до своего блога Урок 9 из 50
Словарь (map) в Go: ключ и значение
Девятый урок курса по Go. Словарь, он же map: пара «ключ — значение», по которой находят сразу, а не перебором. Как записать и прочитать, почему отсутствующий ключ отдаёт ноль, а не ошибку, что такое запятая-ok, зачем Go меняет порядок обхода и чем nil-словарь опаснее nil-среза.
Зачем это нужно
В прошлом уроке список статей рос как надо. Но найти в нём статью по адресу — значит пройти его целиком: сто статей — сто сравнений, тысяча — тысяча. А отвечать браузеру нужно сразу.
Для этого в Go есть второй способ хранить много значений — словарь, в коде map. По-русски его называют и картой, и мапой; в коде он всегда map, и именно это слово стоит в документации и в сообщениях об ошибках.
Образ. Словарь. Вы не читаете его с первой страницы до нужного слова — вы открываете сразу на нужной букве. Список ищет перебором, словарь отвечает сразу.
Сразу целиком
Новая папка, go mod init sabaq08, main.go:
package main
import (
"fmt"
"slices"
)
func main() {
views := map[string]int{
"go": 120,
"казахский": 45,
"веб": 30,
}
views["go"]++
views["степь"] = 7
fmt.Println("тегов:", len(views))
fmt.Println("go:", views["go"])
fmt.Println("музыка:", views["музыка"])
n, ok := views["музыка"]
fmt.Println("значение:", n, "· есть такой ключ:", ok)
keys := make([]string, 0, len(views))
for k := range views {
keys = append(keys, k)
}
slices.Sort(keys)
for _, k := range keys {
fmt.Println(k, views[k])
}
}
Прежде чем запускать — не заглядывая ниже, скажите вслух, что она напечатает. Потом запустите и сверьте: расхождение и есть то место, где вы ещё не знаете языка.
go run . печатает:
тегов: 4
go: 121
музыка: 0
значение: 0 · есть такой ключ: false
go 121
веб 30
казахский 45
степь 7
Тега «музыка» мы не заводили — и всё равно получили ответ.
Разбор
map[string]int — от чего к чему
Запись читается слева направо: в квадратных скобках тип ключа, сразу за ними — тип значения. map[string]int — от строки к числу: по тегу число просмотров.
Ключом может быть не что угодно, а только то, что можно сравнить на равенство: строка, число, bool. Срез ключом быть не может — ровно потому, что два среза Go сравнивать отказывается.
Записать и прочитать
views["степь"] = 7 // записали
n := views["go"] // прочитали
views["go"]++ // прочитали, прибавили, записали обратно
Скобки те же, что у среза, но внутри не номер, а ключ. Никакого append не нужно: присваивание по новому ключу и есть добавление.
Литерал в начале программы — то же самое, только короче: пары через двоеточие, и запятая в конце каждой строки, включая последнюю.
Ключа нет — это не ошибка, а ноль
views["музыка"] напечатал 0, хотя такого тега не существует. Go не остановил программу и не пожаловался: чтение отсутствующего ключа даёт нулевое значение того типа, что стоит в объявлении. Для int это 0, для string — пустая строка, для bool — false.
Образ. Пустая полка на складе. Вы спросили полку «музыка» — вам не сказали «такой нет», вам показали пустую полку. Ничего на ней нет, но и ошибки нет.
На этом держится счётчик из задания: m[tag]++ работает и для тега, которого ещё не было, — он просто начинает с нуля.
Запятая-ok: спросить, есть ли ключ
Иногда разница важна: ноль просмотров и «тега нет» — разные вещи. Тогда у словаря спрашивают двумя переменными:
n, ok := views["музыка"] // 0, false
Второе значение — true, если ключ есть, и false, если нет. Эту форму называют «запятая-ok», и вы встретите её ещё не раз.
Порядок обхода не обещан
range по словарю обходит все пары, но порядок каждый раз может оказаться другим. Это не небрежность, а решение: Go нарочно начинает обход с разного места, чтобы никто не написал код, который держится на порядке.
Проверьте сами: обойдите словарь без сортировки и запустите программу десять раз подряд — вы увидите несколько разных порядков.
Образ. Колода, которую тасуют перед каждой раздачей. Карты те же самые — но кто строил расчёт на их порядке, тот ошибётся.
Нужен порядок — соберите ключи в срез и отсортируйте, как в программе выше. slices.Sort живёт в пакете slices из стандартной библиотеки — имя вам знакомо с прошлого урока. И обратите внимание на make([]string, 0, len(views)): длина ноль, ёмкость известна заранее, ни одного переезда.
delete и len
len(views) — сколько в словаре пар. delete(views, "веб") — убрать пару; если такого ключа нет, ничего не произойдёт, и это тоже не ошибка.
nil-словарь: читать можно, писать нельзя
Здесь словарь ведёт себя не так, как срез, и разницу стоит запомнить сразу.
var m map[string]int
fmt.Println(m["go"]) // 0 — читать можно
m["go"] = 1 // panic: assignment to entry in nil map
В прошлом уроке var titles []string спокойно принимал append. А var m map[string]int записи не принимает: программа останавливается. Словарь нужно создать — make(map[string]int) или литералом map[string]int{}.
Карта урока
Скажите своими словами
Не подглядывая, ответьте вслух или на бумаге. Ответы — в конце урока.
- Почему
views["музыка"]вернул ноль, а не остановил программу? Как отличить «ноль просмотров» от «такого тега нет»? - Зачем Go нарочно меняет порядок обхода словаря?
var titles []stringработает сappend, аvar m map[string]intпадает на первой же записи. В чём разница?
Задание
Обязательное. Напишите функцию count(tags []string) map[string]int, которая считает, сколько раз встретился каждый тег. Вызовите её для []string{"go", "веб", "go", "степь", "go", "веб"} и напечатайте результат в алфавитном порядке ключей — сам словарь вам этого порядка не даст.
По желанию.
has(m map[string]int, k string) bool— через запятую-ok.- Удалите ключ через
deleteи напечатайтеlenдо и после. - Попробуйте записать в
var m map[string]intи прочитайте сообщение об аварии целиком. Уметь читатьpanic— половина отладки.
Куда это встанет в блоге
Маршруты — это словарь: по адресу найти обработчик, не перебирая все. Счётчик просмотров по тегам — словарь. Три языка одной статьи — тоже словарь: по ключу kz, ru или en лежит свой текст, ровно так устроен сайт, который вы сейчас читаете. В уроке про структуры значением станет не число, а целая статья.
Соберите свой блог. Здесь два последних урока встречаются: срез держит порядок статей на главной, а словари — заголовки и объём по адресу. Ни один из двух не смог бы заменить другой, и в step-3 это видно построчно.
Ответы
Показать ответы
- Потому что чтение отсутствующего ключа в Go даёт нулевое значение — для
intэто ноль. Отличить одно от другого можно формой с двумя переменными:n, ok := views["музыка"], гдеokбудетfalse, если ключа нет. - Чтобы никто не полагался на порядок, которого словарь не обещает. Окажись порядок случайно устойчивым, на нём выросли бы программы — и сломались бы при первом же изменении внутреннего устройства. Нужен порядок — соберите ключи в срез и отсортируйте.
- Пустой срез готов к
append: тот сам заводит хранилище и возвращает новый срез. У словаряappend-а нет, запись идёт прямо в хранилище — а его ещё не существует, поэтому программа останавливается. Словарь создают черезmakeили литералом.
Источники
Если вы нашли ошибку или опечатку в тексте статьи, то сообщите нам об этом
Комментарии (0)
Войдите, чтобы оставить комментарий →
Пока нет комментариев. Будьте первым.