День 16 · Срезы: много значений в одной переменной · страница 2 из 7

append: срез растёт

Срез из []int{12, 40, 5} знает свои элементы заранее. А остатки приходят во вводе, по одной строке, и сколько их — неизвестно. Значит, срез должен начинаться пустым и расти по мере чтения.


Пустой срез и append

Ситуация. Принять три остатка по очереди.

Код для чтения · разбираем, набирать не нужно
	var cells []int
	fmt.Println(cells, len(cells))
	cells = append(cells, 12)
	cells = append(cells, 40, 5)
	fmt.Println(cells, len(cells))

Что увидите.

Вывод · это печатает программа, набирать не нужно
[] 0
[12 40 5] 3

Как это читать.

Строка Что происходит
var cells []int срез без элементов: печатается как [], длина 0
cells = append(cells, 12) append берёт срез cells, дописывает 12 в конец и возвращает срез с новым элементом; результат кладётся обратно в cells
append(cells, 40, 5) дописать можно сразу несколько значений, по порядку

Вместо var cells []int можно написать cells := []int{}: оба среза пустые, печатаются как [], и append работает с обоими.

Картинка одного шага:

Образец · набирать не нужно
  до:     cells ──► [ 12 ]                 len 1
  append(cells, 40, 5)  ──► [ 12 40 5 ]    это результат функции
  после:  cells = результат ──► [ 12 40 5 ]   len 3

Форма целиком: срез = append(срез, значение) — имя слева и первым аргументом одно и то же. append отдаёт новый срез, и если его никуда не положить, он пропадёт; почему так — на картинке памяти в день 17.

Одну ошибку компилятор ловит сам — append без присваивания:

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day16/scratch$ go run .
# day16/scratch
./main.go:7:2: append(cells, 12) (value of type []int) is not used

А вот если результат положить не в тот срез — компилятор промолчит.


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

Цель: предсказать четыре числа и сдать ответ.

1. Не запуская, прочитайте строки тела main:

Код для чтения · разбираем, набирать не нужно
	var cells []int
	for i := 1; i <= 3; i++ {
		next := append(cells, i*10)
		fmt.Println(len(next))
	}
	fmt.Println(len(cells))

2. Сдайте четыре числа через пробел, в порядке печати:

▶ Выполните · выделенное замените своим
course answer day16.q2 ЧИСЛО ЧИСЛО ЧИСЛО ЧИСЛО

3. Проверьте запуском:

✎ Наберите в файл scratch/main.go
package main

import "fmt"

func main() {
	var cells []int
	for i := 1; i <= 3; i++ {
		next := append(cells, i*10)
		fmt.Println(len(next))
	}
	fmt.Println(len(cells))
}
▶ Выполните
cd ~/gocourse/day16/scratch
go run .

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


Разбор. На каждом проходе append берёт пустой cells, дописывает одно число и отдаёт срез длины 1 — он попадает в next и забывается. В cells результат не вернулся ни разу. Программа собралась, go vet молчит — ошибка видна только по выводу.


Весь ввод — в срез

Теперь соединим append с чтением из дня 07. Программа целиком:

Код для чтения · разбираем, набирать не нужно
package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	var cells []int
	for scanner.Scan() {
		qty, err := strconv.Atoi(strings.TrimSpace(scanner.Text()))
		if err != nil {
			fmt.Fprintln(os.Stderr, "Ошибка: не число:", scanner.Text())
			os.Exit(1)
		}
		cells = append(cells, qty)
	}
	fmt.Println(cells, len(cells))
}

Что увидите.

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day16/scratch$ printf '12\n40\n5\n' | go run .
[12 40 5] 3
stagiaire@lab:~/gocourse/day16/scratch$ go run . < /dev/null
[] 0

Как это читать. Срез объявлен до цикла: он должен пережить все проходы. Цикл чтения — тот же, что в дне 07: пока есть строка, разобрать число, при ошибке сообщить и выйти. Новая только строка с append: каждое прочитанное число встаёт в конец. После цикла весь ввод лежит в cells, по порядку. Это заготовка для задания stock на пятой странице.


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

Цель: программа из раздела выше печатает срез из вашего ввода.

1. Наберите программу из раздела «Весь ввод — в срез» в scratch/main.go — целиком, вместе с блоком import:

✎ Наберите в файл scratch/main.go
package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	var cells []int
	for scanner.Scan() {
		qty, err := strconv.Atoi(strings.TrimSpace(scanner.Text()))
		if err != nil {
			fmt.Fprintln(os.Stderr, "Ошибка: не число:", scanner.Text())
			os.Exit(1)
		}
		cells = append(cells, qty)
	}
	fmt.Println(cells, len(cells))
}

2. Запустите с вводом и без:

▶ Выполните
cd ~/gocourse/day16/scratch
printf '12\n40\n5\n' | go run .
go run . < /dev/null

3. Перенесите внутрь цикла две строки: var cells []int — первой строкой тела, fmt.Println(cells, len(cells)) — последней. Запустите с тем же вводом:

▶ Выполните
printf '12\n40\n5\n' | go run .
Вывод · это печатает программа, набирать не нужно
[12] 1
[40] 1
[5] 1

Верните обе строки на место.

Готово, когда: программа печатает [12 40 5] 3, а опыт из шага 3 вы объяснили себе словами.


Что может пойти не так

Что видите Что это значит Что делать
append(cells, 12) (value of type []int) is not used результат append никуда не положен cells = append(cells, 12)
invalid append: argument must be a slice; have n (variable of type int) первым аргументом стоит число, а не срез первым — срез, за ним значения
cannot use "7" (untyped string constant) as int value in argument to append в срез целых дописывают строку тип значения — как у элементов среза
panic: runtime error: index out of range [0] with length 0 в пустой срез пишут по индексу: cells[0] = 12 элемента 0 ещё нет — новый элемент добавляет только append
в срезе всё время одно число: [12] 1, [40] 1, [5] 1 срез объявлен внутри цикла и создаётся заново на каждом проходе объявление — до цикла
длина всё время 1, а после цикла срез пустой результат append ушёл в новую переменную — next := append(…) или cells := append(…) внутри цикла cells = append(cells, …) со знаком =

Второй вариант из последней строки — cells := append(…) — тот же дефект затенения, что в дне 12: := внутри блока создаёт новую переменную с тем же именем.


Попробуйте сейчас: починить fix1.

Цель: программа приёмки считает все ячейки и их сумму.

1. Запустите заготовку:

▶ Выполните
cd ~/gocourse/day16/fix1
cat example.txt
go run . < example.txt

Что сейчас получается:

Вывод · это печатает программа, набирать не нужно
Принята ячейка 1
Принята ячейка 1
Принята ячейка 1
Ячеек: 0
Всего: 0 шт.

2. Найдите строку, где результат append уходит не туда, и почините её — одна-две строки, не больше. Номера ячеек должны расти: 1, 2, 3.

3. Проверьте и закоммитьте:

▶ Выполните
go run . < example.txt
diff ~/.course/orig/day16/fix1/main.go main.go
cd ~/gocourse/day16
git add .
git commit -m "fix1: срез после append"

Готово, когда: программа печатает Ячеек: 3 и Всего: 57 шт., diff показывает одну изменённую строку — пункт fix1.


Дальше: как пройти по срезу и что бывает, если выйти за границу — course next