День 18 · map: таблица по ключу · страница 1 из 6

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]

Как это читать. Считает верно, но дорого:

Нужна таблица, в которой по артикулу сразу получаешь число.


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.go
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)
}

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.go
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))
}
▶ Выполните
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