map: значение по ключу
Срез из дня 16 хранит значения по порядку, и добраться до значения можно по номеру. На складе же ищут по артикулу: «сколько осталось K-417?». Для таблиц «артикул → число» в Go есть отдельный тип — map.
Срезом неудобно
Ситуация. Сканер на приёмке пишет артикул каждой коробки. Нужно узнать, сколько коробок пришло по каждому артикулу. Срезами: один срез для артикулов, второй для счётчиков и поиск по первому на каждую коробку.
package main
import "fmt"
func main() {
skus := []string{"P-305", "K-417", "P-305", "M-120", "P-305"}
var seen []string
var counts []int
for _, sku := range skus {
found := -1
for i := 0; i < len(seen); i++ {
if seen[i] == sku {
found = i
}
}
if found == -1 {
seen = append(seen, sku)
counts = append(counts, 1)
} else {
counts[found]++
}
}
fmt.Println(seen, counts)
}Что увидите.
stagiaire@lab:~/gocourse/day18/scratch$ go run .
[P-305 K-417 M-120] [3 1 1]Как это читать. Считает верно, но дорого:
- два среза обязаны идти в ногу: номер артикула в
seen— номер его счётчика вcounts; - на каждую коробку — цикл по всем встреченным артикулам, и ещё один такой же, чтобы потом спросить «сколько у
K-417?».
Нужна таблица, в которой по артикулу сразу получаешь число.
map[string]int
package main
import "fmt"
func main() {
stock := map[string]int{"K-417": 12, "M-120": 0}
stock["P-305"] = 7
stock["K-417"] = stock["K-417"] - 2
fmt.Println(stock["K-417"], stock["P-305"], len(stock))
fmt.Println(stock)
}Что увидите.
stagiaire@lab:~/gocourse/day18/scratch$ go run .
10 7 3
map[K-417:10 M-120:0 P-305:7]Как это читать.
| Запись | Что значит |
|---|---|
map[string]int |
тип: ключи — строки, значения — целые числа |
{"K-417": 12, "M-120": 0} |
литерал: пары «ключ: значение» через запятую |
stock["P-305"] = 7 |
запись. Ключа не было — пара добавилась; был бы — значение заменилось |
stock["K-417"] - 2 |
чтение: значение при ключе "K-417" |
len(stock) |
сколько в map пар |
map[K-417:10 M-120:0 P-305:7] |
так Println печатает map: пары через пробел, ключ и значение через двоеточие |
stock
┌─────────┬──────────┐
│ ключ │ значение │
├─────────┼──────────┤
│ "K-417" │ 10 │
│ "M-120" │ 0 │
│ "P-305" │ 7 │
└─────────┴──────────┘В срезе значение адресуется номером, и номера идут подряд. В map — ключом, который выбираете вы, и ключ в map один: вторая запись с тем же ключом заменяет значение.
Порядка у пар нет. Println печатает ключи по возрастанию, потому что так устроена печать в пакете fmt, — сама map пары в таком порядке не держит (страница 4). По-русски map зовут «словарём»; в курсе пишем просто map, чтобы не путать со словариком ~/errors.md.
Пустая map: {} или make
package main
import "fmt"
func main() {
counts := make(map[string]int)
fmt.Println(len(counts), counts)
counts["K-417"] = 3
fmt.Println(len(counts), counts)
}Что увидите.
0 map[]
1 map[K-417:3]make(map[string]int) и map[string]int{} дают одно и то же — пустую map, в которую можно писать. Похожая запись var counts map[string]int ведёт себя иначе — это пятая страница.
Попробуйте сейчас: первая map.
Цель: своими руками записать, заменить и прочитать значения в map.
1. Наберите в черновик:
✎ Наберите в файлscratch/main.gopackage main import "fmt" func main() { stock := map[string]int{"K-417": 12, "M-120": 0} stock["P-305"] = 7 stock["K-417"] = stock["K-417"] - 2 fmt.Println(stock["K-417"], stock["P-305"], len(stock)) fmt.Println(stock) }2. Запустите:
▶ Выполнитеcd ~/gocourse/day18/scratch go run .3. Перед первым
Printlnдопишите строкуstock["M-120"] = 5и запустите снова: пара заменилась или добавилась?Готово, когда: первый запуск совпал с разделом «Что увидите», а после шага 3 длина осталась 3, и у
M-120стоит 5.
Ключа нет
Что вернёт map на ключ, которого в ней нет? Сначала предскажите.
Попробуйте сейчас: чего нет в map.
Цель: предсказать три числа, не запуская программу, и сдать ответ.
1. Не запуская, прочитайте программу. Ключа
"T-999"в литерале нет.Код для чтения · разбираем, набирать не нужноpackage main import "fmt" func main() { stock := map[string]int{"K-417": 12, "M-120": 0} fmt.Println(stock["M-120"], stock["T-999"], len(stock)) }2. Сдайте три числа через пробел, в порядке печати:
▶ Выполните · выделенное замените своимcourse answer day18.q1 ЧИСЛО ЧИСЛО ЧИСЛО3. Проверьте запуском — замените программу в черновике:
✎ Наберите в файлscratch/main.gopackage main import "fmt" func main() { stock := map[string]int{"K-417": 12, "M-120": 0} fmt.Println(stock["M-120"], stock["T-999"], len(stock)) }▶ Выполнитеgo run .Вывод и разбор — под блоком.
Готово, когда: пункт
q1вcourse check 18зелёный.
Что увидите.
0 0 2Как это читать. Чтение ключа, которого нет, — не ошибка и не паника. Map отдаёт нулевое значение своего типа значений: 0 для int, "" для string, false для bool. Пара при чтении не появляется: len(stock) остался 2.
Но M-120 в каталоге есть и закончился, а T-999 в каталоге нет вовсе — и оба дали ноль. Как их различить — следующая страница.
Что может пойти не так
| Что видите | Что это значит | Что делать |
|---|---|---|
duplicate key "K-417" in map literal |
в литерале один ключ записан дважды | оставить одну пару |
syntax error: unexpected newline in composite literal; possibly missing comma or } |
в литерале на несколько строк нет запятой после последней пары | запятая после каждой пары, как в таблице случаев дня 14 |
cannot use 417 (untyped int constant) as string value in map index |
ключи у map строковые, а в скобках число | ключ в кавычках: stock["K-417"] |
| печатается 0, хотя артикул точно есть | ключ набран не так: "K-417" и "К-417" с русской К — разные строки |
скопировать ключ из данных |
Попробуйте сейчас: репозиторий дня.
Цель: в
~/gocourse/day18есть репозиторий с первым коммитом — заготовками дня. Каждое задание дальше заканчивается своим коммитом.▶ Выполнитеcd ~/gocourse/day18 git init git add . git commit -m "день 18: заготовки" git log --onelineГотово, когда:
git log --onelineпечатает одну строку с вашим сообщением. Если git просит подпись автора —git config --global user.nameиuser.email, как в день 11.
Дальше: как отличить ноль от отсутствующего ключа и как удалить пару — course next