День 20 · Контрольная точка 4: срезы, map, строки и файлы · страница 1 из 3

Памятка блока 4

Справочник по дням 16–19: срезы, общий массив, map, строки и руны, файлы. Читать подряд не нужно — обязателен только блок «Попробуйте сейчас». Остальное открывайте, когда понадобится: названия разделов совпадают со ссылками в задачах контрольной и в подсказках. Все сообщения в таблицах — со страниц дней, Go 1.27.1. Правила трассировки — на второй странице.


Попробуйте сейчас: словарик до сорока разборов.

Цель: в ~/errors.md не меньше сорока разборов.

1. Недостающие сообщения получите сами: в черновике ломайте программу способами из таблиц «Ошибки компилятора» и «Паники» ниже, по одному за раз. После каждой поломки — запуск:

▶ Выполните
cd ~/gocourse/day20/scratch
go run .

2. Строка словарика — сообщение так, как его напечатал Go, тире, что это значило в вашем коде. Двоеточие вместо тире не засчитывается.

3. Пересказ вместо сообщения не считается: «колонка разъехалась» — это не текст ошибки. Слева копируется строка ответа целиком, хоть от компилятора, хоть от go vet, хоть от паники или упавшего теста.

Готово, когда: пункт errors в course check 20 зелёный.


Команды и готовые функции

Что Что делает День
append(s, x) новый срез с x в конце; результат присваивают: s = append(s, x) 16
len(s), cap(s) длина и ёмкость среза; у строки len — байты 16, 17, 19
s[a:b], s[:n], s[a:] подсрез — окно на тот же массив 17
make([]int, n), make([]int, 0, n) срез длины n; пустой срез с ёмкостью n 17
copy(dst, src) переписать элементы; сколько — меньшая из двух длин 17
slices.Clone(s) копия среза одной строкой 17
slices.Sort(s) сортирует сам срез, ничего не возвращает 17
slices.Contains, slices.Index есть ли значение; его индекс или -1 17
slices.Max, slices.Min наибольший и наименьший; на пустом срезе — паника 17
slices.Equal(a, b) равны ли срезы поэлементно 17
make(map[string]int), map[string]int{} создать пустую map 18
v, ok := m[k] значение и признак «ключ есть» 18
delete(m, k), len(m) убрать пару; сколько пар 18
slices.Sorted(maps.Keys(m)) ключи map по возрастанию — готовая связка 18
maps.Equal(a, b) равны ли две map 18
utf8.RuneCountInString(s) сколько букв (рун) в строке 19
[]rune(s), string(r) срез рун; руна или срез рун обратно в строку 19
strings.Split(s, ";"), strings.Fields(s) резать по разделителю; по пробелам любой длины 19
strings.TrimSpace, strings.TrimSuffix(s, "\n") срезать пробелы по краям; убрать хвост 19
strings.Repeat(".", n) строка из n точек 19
os.ReadFile("orders.csv") весь файл сразу, байтами 19
os.Open, bufio.NewScanner(f), f.Close() файл построчно 19
go doc slices.Sort справка, не выходя из терминала 17
wc -c, wc -m байты и знаки в файле 19

Срезы — день 16

Код для чтения · разбираем, набирать не нужно
stock := []int{12, 40, 5}
stock = append(stock, 7)
for i, qty := range stock {
	fmt.Println(i+1, qty)
}
Что Правило
индексы с 0 до len(s) - 1; номер для человека — i + 1
append результат — обратно в тот же срез: s = append(s, x)
for i, v := range s i — индекс, v — копия элемента; одна переменная в range — это индекс
массив [5]int длина — часть типа; в работе почти всегда срез
пустой срез []int{} или var s []int; append работает с обоими
срез в функции элементы, изменённые внутри, видны снаружи
сравнение == у срезов нет: длина и поэлементно или slices.Equal

Срезы изнутри — день 17

Срез — это заголовок из трёх чисел и массив под ним. Подсрез — ещё одно окно на тот же массив:

Образец · набирать не нужно
массив:    [ 7 | 4 | 9 | 2 ]
stock      ^---------------^   len 4, cap 4
head       ^-------^           len 2, cap 4   head := stock[:2]
part           ^-------^       len 2, cap 3   part := stock[1:3]
Что происходит Что видно
запись head[1] = 5 меняется ячейка массива — stock видит ту же 5
append(head, x), у head len < cap x пишется в следующую ячейку того же массива — поверх stock[2]
append, у среза len == cap новый массив; дальше срез ни с кем ячеек не делит
рост cap при новом массиве деталь реализации: не угадывать, при нужде печатать
два append от одного среза с запасом ёмкости второй затирает то, что дописал первый, — главный дефект дня
slices.Clone(s) или make и copy своя копия: запись в неё исходный срез не трогает
срез в функции копируется заголовок: элементы общие, длина — нет; append внутри функции наружу не виден, но может записать в общий массив

nil-срез (var s []int) и пустой срез ([]int{}): у обоих len 0, append работает с обоими, fmt.Println печатает [].


map — день 18

Код для чтения · разбираем, набирать не нужно
counts := map[string]int{}
counts["T-118"]++
qty, ok := counts["P-305"]
for _, sku := range slices.Sorted(maps.Keys(counts)) {
	fmt.Println(sku, counts[sku])
}
Что Как
создать m := map[string]int{}, make(map[string]int), литерал {"K-417": 12}
записать и прочитать m[k] = v, x := m[k]; нет ключа — нулевое значение
есть ли ключ v, ok := m[k] — ok ложен, только если ключа нет вовсе
счётчик и сумма m[k]++, m[k] += q — без проверки «есть ли уже»
обход for k, v := range m — порядок случайный, от запуска к запуску разный
печать по порядку for _, k := range slices.Sorted(maps.Keys(m))
nil-map var m map[string]int: читать можно, запись — паника
в функции та же map: запись внутри видна снаружи
ключ только сравнимый тип: строка, число; срез — нельзя
срез или map важен порядок и номер — срез; поиск по ключу — map

Строки и руны — день 19

Строка — байты в UTF-8. Русская буква занимает два байта; латинская буква, цифра и пробел — один:

Образец · набирать не нужно
"Болт М8"   букв 7, байтов 12
 Б  о  л  т     М  8
 2  2  2  2  1  2  1   байтов на знак
 0  2  4  6  8  9  11  позиция начала — это i в for i := range
Что Что даёт
len(s) байты, а не буквы
s[i] байт; печатается числом вроде 208
s[:n] режет по байтам — может разрезать букву пополам: �
for i, r := range s r — руна (буква), i — номер байта, с которого она начинается
utf8.RuneCountInString(s), len([]rune(s)) длина в буквах
string([]rune(s)[:n]) первые n букв
fmt.Println(r) для руны число; букву печатает string(r)
strings.ToLower, strings.Contains, strings.HasPrefix с русскими буквами работают верно
fmt.Sprintf("%-10s", s), fmt.Sprintf("%.3s", s) ширина и точность в fmt считаются в буквах: дописать пробелами до 10 букв, оставить 3 буквы

Колонка по буквам — как fitName дня 19: короче ширины — дописать, длиннее — первые ширина − 1 букв и ~. Длина везде в буквах.


Строки и файлы — день 19

Код для чтения · разбираем, набирать не нужно
data, err := os.ReadFile("orders.csv")
if err != nil {
	fmt.Fprintln(os.Stderr, "ошибка:", err)
	os.Exit(1)
}
text := strings.TrimSuffix(string(data), "\n")
lines := strings.Split(text, "\n")
Что Правило
имя файла ищется в текущем каталоге — там, где запущена программа
перевод строки в конце файла не строка: убрать TrimSuffix до Split, иначе последний кусок пустой
bufio.Scanner по файлу перевод строки в конце учитывает сам; файл закрыть f.Close() — удобнее это делает defer, он будет в блоке 6
строка выгрузки strings.Split(line, ";"), у каждого поля TrimSpace; число полей проверить до обращения к ним
битая строка сообщение с номером i+1 в поток ошибок, continue, программа идёт дальше
код возврата os.Exit(1) последней строкой main, если битые строки были, — после отчёта
encoding/csv готовый разборщик — в дополнительном чтении

Тесты в блоке 4

Что Как
таблица случаев это срез: []struct{…}{…}; что внутри struct — блок 5
сравнить срезы в тесте slices.Equal(got, want), а не !=
сравнить map maps.Equal(got, want)
функция не меняет вход после вызова сравнить вход с отдельным литералом, не с before := stock
функция разбора строки случаи: верная строка, каждая ошибка, граница; русские названия — чтобы байты и буквы различались
проверка подменой тест обязан проходить на верной функции и на другой её записи и падать на неверной
функция и тест функция объявлена в своём файле один раз; в тесте её только вызывают

Ошибки компилятора

Сообщение Причина День
invalid argument: index 5 out of bounds [0:5] индекс-константа за концом массива 16
append(cells, 12) (value of type []int) is not used результат append не присвоен 16
invalid append: argument must be a slice; have n (variable of type int) первым аргументом append — не срез 16
invalid operation: got != want (slice can only be compared to nil) срезы сравнили оператором 16
cannot use cells (variable of type [3]int) as []int value in argument to total передан массив, а ждут срез 16
invalid slice indices: 1 < 3 начало подсреза больше конца, оба числа в коде 17
invalid argument: length and capacity swapped в make длина больше ёмкости 17
slices.Sort(stock) (no value) used as value результат Sort присвоили, а он ничего не возвращает 17
undefined: slices нет import "slices" 17
duplicate key "K-417" in map literal ключ записан в литерале дважды 18
cannot use 417 (untyped int constant) as string value in map index ключ не того типа 18
invalid map key type []int ключ — срез 18
invalid operation: got != want (map can only be compared to nil) map сравнили оператором 18
in call to slices.Sorted, type map[string]int of stock does not match iter.Seq[E] (cannot infer E) в slices.Sorted передали саму map, без maps.Keys 18
declared and not used: ok взяли ok и не проверили 18
invalid argument: index 8 out of bounds [0:8] индекс строки-константы за концом, считается в байтах 19
cannot assign to s[0] (neither addressable nor a map index expression) строку поменяли по индексу: строки не меняются 19
invalid operation: r + "!" (mismatched types rune and untyped string) руну склеили со строкой 19
cannot use data (variable of type []byte) as string value in argument to strings.Split ReadFile отдаёт байты 19
undefined: utf8 нет import "unicode/utf8" 19

Паники

Программа собралась, но упала при запуске; код возврата 2.

Сообщение Причина День
panic: runtime error: index out of range [5] with length 5 индекс равен длине; последний — len - 1 16
panic: runtime error: index out of range [0] with length 0 в пустой срез пишут по индексу вместо append 16
panic: runtime error: slice bounds out of range [:7] with capacity 5 конец подсреза дальше конца массива 17
panic: slices.Max: empty list максимум пустого среза 17
panic: assignment to entry in nil map запись в map, объявленную через var без создания 18
panic: strings: negative Repeat count ширину дополнения посчитали через len 19
open orders.csv: no such file or directory не паника, а ошибка ReadFile: файла в текущем каталоге нет 19

Собирается, но считает не то

Признак Дефект День
после цикла срез пустой или в нём одно значение результат append ушёл в новую переменную или срез объявлен в цикле 16
сумма 0, 1, 3, 6 в range одна переменная — это индекс 16
элемент одного среза «сам» поменялся append в соседний срез писал в общий массив 17
на одних вводах портит, на других нет всё решает ёмкость 17
исходный срез отсортировался slices.Sort по самому срезу, а не по копии 17
товар с нулевым остатком «нет в каталоге» условие смотрит на значение, а не на ok 18
первый запуск совпал с образцом, второй — нет печать прямо из range по map 18
один артикул посчитан двумя парами пробел в ключе: нет TrimSpace 18
колонка ровная у латиницы, кривая у русских длина через len 19
в выводе � строка обрезана по байтам 19
лишняя битая строка с номером за концом файла Split без TrimSuffix 19
нет строки Итого, а битые строки были os.Exit(1) раньше печати итога 19

Дальше: правила трассировки 1–32 — course next