Памятка блока 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