Обход map: порядок не гарантирован
Посчитать — полдела: итоги нужно напечатать. Обойти map можно тем же for … range, что и срез, но у обхода map есть свойство, которое ломает отчёты и тесты.
for sku, qty := range stock
Ситуация. Напечатать остатки всех артикулов из map.
package main
import "fmt"
func main() {
stock := map[string]int{
"P-305": 7,
"K-417": 12,
"M-120": 0,
"A-210": 5,
"T-118": 3,
}
for sku, qty := range stock {
fmt.Println(sku, qty)
}
}Что увидите. Два запуска подряд, программа и данные те же:
stagiaire@lab:~/gocourse/day18/scratch$ go run .
M-120 0
A-210 5
T-118 3
P-305 7
K-417 12
stagiaire@lab:~/gocourse/day18/scratch$ go run .
K-417 12
M-120 0
A-210 5
T-118 3
P-305 7Как это читать. for sku, qty := range stock проходит по всем парам: sku — ключ, qty — значение. Нужен только ключ — for sku := range stock, только значение — for _, qty := range stock.
Порядок обхода map не определён — так записано в описании языка. Он может отличаться от запуска к запуску и от обхода к обходу, и опираться на него нельзя. Совпадение двух запусков ничего не доказывает.
Где порядок не важен — обходите смело: сумма остатков при любом порядке одна.
package main
import "fmt"
func main() {
stock := map[string]int{"K-417": 12, "M-120": 0}
total := 0
for _, qty := range stock {
total += qty
}
fmt.Println(total)
}12Где важен — отчёт, вывод, который сверяют с образцом, тест по строкам, — порядок задаёте вы.
Печать по возрастанию: готовая связка
package main
import (
"fmt"
"maps"
"slices"
)
func main() {
stock := map[string]int{
"P-305": 7,
"K-417": 12,
"M-120": 0,
"A-210": 5,
"T-118": 3,
}
for _, sku := range slices.Sorted(maps.Keys(stock)) {
fmt.Println(sku, stock[sku])
}
}Что увидите. Два запуска — один и тот же вывод:
stagiaire@lab:~/gocourse/day18/scratch$ go run .
A-210 5
K-417 12
M-120 0
P-305 7
T-118 3
stagiaire@lab:~/gocourse/day18/scratch$ go run .
A-210 5
K-417 12
M-120 0
P-305 7
T-118 3Как это читать. Изнутри наружу:
| Запись | Что делает |
|---|---|
maps.Keys(stock) |
отдаёт ключи map по одному — в том же неопределённом порядке |
slices.Sorted(…) |
собирает ключи в срез []string и сортирует по возрастанию |
for _, sku := range … |
обычный обход среза из дня 16: _ — номер, sku — ключ |
stock[sku] |
значение берём из map по ключу |
"maps", "slices" в import |
пакеты стандартной библиотеки |
slices.Sorted(maps.Keys(m)) пишите целиком, как одну форму — как форму таблицы тестов в дне 14. Что возвращает maps.Keys, в курсе будет позже. Если ключи уже лежат в срезе, его сортирует slices.Sort из дня 17.
Справка сама предупреждает о порядке:
stagiaire@lab:~/gocourse/day18/scratch$ go doc maps.Keys
package maps // import "maps"
func Keys[Map ~map[K]V, K comparable, V any](m Map) iter.Seq[K]
Keys returns an iterator over keys in m. The iteration order is not
specified and is not guaranteed to be the same from one call to the next.
Скобки [Map ~map[K]V, …] пока не разбираем: они значат «map с любыми ключами и значениями». Важна фраза под заголовком: порядок не задан.
Попробуйте сейчас: печать по возрастанию.
Цель: увидеть разный порядок своими глазами и получить одинаковый вывод связкой.
1. Наберите в черновик программу с обходом
range stock:✎ Наберите в файлscratch/main.gopackage main import "fmt" func main() { stock := map[string]int{ "P-305": 7, "K-417": 12, "M-120": 0, "A-210": 5, "T-118": 3, } for sku, qty := range stock { fmt.Println(sku, qty) } }2. Запустите три раза и сравните:
▶ Выполнитеcd ~/gocourse/day18/scratch go run . go run . go run .3. Замените обход связкой и запустите дважды:
✎ Наберите в файлscratch/main.gopackage main import ( "fmt" "maps" "slices" ) func main() { stock := map[string]int{ "P-305": 7, "K-417": 12, "M-120": 0, "A-210": 5, "T-118": 3, } for _, sku := range slices.Sorted(maps.Keys(stock)) { fmt.Println(sku, stock[sku]) } }▶ Выполнитеgo run . go run .4. Прочитайте справку по обеим частям связки:
▶ Выполнитеgo doc maps.Keys go doc slices.SortedГотово, когда: на шаге 2 порядок хотя бы раз отличался (если нет — запустите ещё), на шаге 3 оба вывода начинаются с
A-210, пунктt_docзелёный.
Попробуйте сейчас: задание tally.
Цель: программа
~/gocourse/day18/tallyсчитает коробки по артикулам через map и печатает итоги по возрастанию артикула.1. Прочитайте условие — артикулы у вас свои:
▶ Выполнитеcd ~/gocourse/day18/tally cat TASK.txt2. Замените заготовку
main.goсвоей программой: чтение до конца ввода иTrimSpace— страница 3, пустую строку пропуститьcontinue, счётчикcounts[sku]++, печать — связкой иfmt.Printf, число разных артикулов —len(counts).3. Запустите на примере дважды и на пустом вводе:
▶ Выполнитеgo run . < example.txt go run . < example.txt printf '' | go run .4. Закоммитьте:
▶ Выполнитеcd ~/gocourse/day18 git add . git commit -m "tally: подсчёт через map"Готово, когда: оба запуска совпадают с выводом из
TASK.txt, пустой ввод печатаетАртикулов: 0— пунктtally. Проверка подаст другие артикулы в другом порядке.
Попробуйте сейчас: задание group.
Цель: в
~/gocourse/day18/groupфункцияtotalsскладывает приход по артикулам, а программа печатает итоги по возрастанию артикула.1. Прочитайте условие и готовое чтение ввода:
▶ Выполнитеcd ~/gocourse/day18/group cat TASK.txt cat main.go2. В
totals.goвместоreturn nil— тело функции: пустая map, цикл поskusс номером,+=, как в «Сумме по группе» на странице 3, иreturn.3. В конце
mainуберите временную строку и напишите печать: связкой по ключамsums, по строке на артикул, и сумму всех значений последней строкой. Вimportдобавьте"maps"и"slices".4. Запустите дважды и закоммитьте:
▶ Выполнитеgo run . < example.txt go run . < example.txt cd ~/gocourse/day18 git add . git commit -m "group: totals и печать"Готово, когда: оба запуска совпадают с выводом из
TASK.txt, артикул с количеством 0 в нём есть — пунктgroup. Тест кtotals— на следующей странице.
Что может пойти не так
| Что видите | Что это значит | Что делать |
|---|---|---|
cannot use sku (variable of type int) as string value in map index |
for sku := range slices.Sorted(…): у среза первым идёт номер |
for _, sku := range … |
in call to slices.Sorted, type map[string]int of stock does not match iter.Seq[E] (cannot infer E) |
в slices.Sorted передали саму map |
slices.Sorted(maps.Keys(stock)) |
undefined: maps |
в import нет "maps" |
добавить "maps", так же "slices" |
| первый запуск совпал с образцом, второй — нет | печать прямо из range по map |
обходить отсортированные ключи |
| проверка: строки те же, но в другом порядке | то же самое на скрытом вводе | связка slices.Sorted(maps.Keys(…)) |
Дальше: map, которую забыли создать, map в функции и тест, — course next