Обход map
В настоящей телефонной книге имена стоят по алфавиту. Наша книга-map устроена иначе: в ней нет порядка записей. Найти номер по имени это не мешает. Мешает это только тому, кто листает книгу целиком и ждёт, что записи пойдут по порядку.
Цикл range проходит все пары map, но не обязательно в том порядке, в котором их записали. Если напечатать пары в цикле, порядок строк может меняться от запуска к запуску. Результат, который от порядка не зависит, получается одинаковым всегда — например, сумма:
package main
import "fmt"
func main() {
stock := map[string]int{"pear": 3, "plum": 5, "kiwi": 2}
total := 0
for _, n := range stock {
total += n
}
fmt.Println(total) // 10
}Цикл берёт пары в каком-то порядке, но каждое значение попадает в сумму ровно один раз. Поэтому при любом порядке total равно 3 + 5 + 2, и программа всегда печатает 10.
Три формы цикла
Цикл range по map пишут в трёх формах — в зависимости от того, что нужно из каждой пары.
Ключ и значение
В записи for k, v := range m переменная k получает ключ, а v — значение. Эту форму берут, когда нужно и то и другое, например чтобы напечатать пары.
Только ключи
Если значения не нужны, пишут for k := range m: вторую переменную опускают. Так собирают ключи в срез, как в разделе об обходе по ключам ниже.
Только значения
Если не нужны ключи, на их место ставят _: for _, v := range m. Так первый пример считает сумму. Ключ здесь нельзя опустить, как значение: первая переменная в range всегда получает ключ.
Порядок не задан
Язык не задаёт порядок, в котором приходят пары. Go меняет его намеренно: каждый обход начинается со случайного места. Поэтому два запуска программы и даже два цикла подряд в одной программе могут пройти пары по-разному.
Go делает это специально, чтобы код не зависел от внутреннего устройства map. От порядка не зависят, например:
- сумма значений;
- количество пар;
- наибольшее значение.
Этот вопрос часто задают на собеседовании: «В каком порядке range обходит map?» Ответ: порядок не задан, и Go нарочно делает его случайным. Если нужен стабильный порядок, задайте его сами — об этом следующий раздел.
Обход по ключам
Когда порядок нужен, ключи собирают в срез и сортируют функцией slices.Sort из пакета slices (import "slices"). Потом цикл идёт уже по срезу и читает значения из map по ключу:
stock := map[string]int{"pear": 3, "plum": 5, "kiwi": 2}
keys := make([]string, 0, len(stock))
for k := range stock {
keys = append(keys, k)
}
slices.Sort(keys)
fmt.Println(keys) // [kiwi pear plum]
for _, k := range keys {
fmt.Print(k, ":", stock[k], " ") // kiwi:2 pear:3 plum:5
}Давайте разберём пример по строкам:
make([]string, 0, len(stock))создаёт пустой срез с ёмкостью на все ключи.- Первый цикл кладёт в него ключи в случайном порядке.
slices.Sort(keys)расставляет их по алфавиту, иPrintlnпечатает[kiwi pear plum].- Второй цикл идёт по отсортированному срезу и для каждого ключа читает значение из
map.
Что делает slices.Sort:
- меняет порядок элементов в самом срезе и ничего не возвращает;
- строки упорядочивает по алфавиту, но заглавные буквы идут раньше строчных;
- числа упорядочивает по возрастанию;
- саму
mapне трогает: сортирует только срез её ключей.
Для среза строк то же делает sort.Strings(keys) из пакета sort.
Печать map целиком
Вызов fmt.Println(stock) печатает map[kiwi:2 pear:3 plum:5]: перед печатью пакет fmt сам сортирует ключи. Цикл range этого не делает.
Частая ошибка
Ключи — числа 1, 2, 3, и от цикла ждут, что пары придут по возрастанию:
names := map[int]string{1: "pear", 2: "plum", 3: "kiwi"}
for k, v := range names { // ✗
fmt.Println(k, v)
}Строки выходят в любом порядке, и он может отличаться от запуска к запуску. На маленькой map порядок часто совпадает с ожидаемым, поэтому ошибку долго не замечают. Программа проходит проверку на одном запуске и не проходит на следующем. Соберите ключи в срез и отсортируйте их, как показано выше.
Главное
rangeпоmapвыдаёт пары в незаданном порядке, и Go нарочно делает его случайным.- Сумма, число пар или максимум от порядка не зависят и получаются одинаковыми всегда.
- Для стабильного порядка ключи собирают в срез, сортируют через
slices.Sortи читают значения по ключам.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.