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

Накопители и максимум по срезу

В дне 08 сумму и максимум считали на лету: прочитал число — добавил, сравнил, забыл. Со срезом данные остаются, и по ним можно пройти второй раз. Иногда без этого задачу не решить.


Сумма и максимум — те же накопители

Ситуация. Итог по стеллажу: сколько штук всего и в каких ячейках лежит больше всего.

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

import "fmt"

func main() {
	stock := []int{12, 40, 5, 40, 7}
	sum := 0
	most := stock[0]
	for _, qty := range stock {
		sum += qty
		if qty > most {
			most = qty
		}
	}
	fmt.Println("Всего:", sum, "шт.")
	fmt.Print("Больше всего: ", most, " шт., ячейки:")
	for i, qty := range stock {
		if qty == most {
			fmt.Print(" ", i+1)
		}
	}
	fmt.Println()
}

Что увидите.

Вывод · это печатает программа, набирать не нужно
Всего: 104 шт.
Больше всего: 40 шт., ячейки: 2 4

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

Кусок Что делает
sum := 0 и sum += qty накопитель суммы из дня 08, только числа берутся из среза
most := stock[0] максимум начинается с первого элемента, а не с нуля: первый элемент — всегда настоящий кандидат. Флаг «первое число» из дня 08 больше не нужен
первый цикл находит сумму и максимум
второй цикл идёт по тому же срезу ещё раз и печатает номера всех ячеек с максимумом
fmt.Print печать без перевода строки: номера дописываются в одну строку, а fmt.Println() в конце её закрывает

Второй цикл — главное: пока ячейки читаются по одной, неизвестно, окажется ли 40 максимумом — дальше может прийти 60. Номера ячеек с максимумом называют после первого прохода, и для этого данные хранят в срезе.


Пустой срез и первый элемент

most := stock[0] берёт элемент, который обязан существовать. Если ввод пустой, срез пустой, и программа падает на первой же строке — вот три строки тела main, где это видно:

Код для чтения · разбираем, набирать не нужно
	var stock []int
	most := stock[0]
	fmt.Println(most)
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day16/scratch$ go run .
panic: runtime error: index out of range [0] with length 0

goroutine 1 [running]:
main.main()
	/home/stagiaire/gocourse/day16/scratch/main.go:7 +0x9
exit status 2

Поэтому пустой случай проверяют до того, как обращаться к элементам:

Код для чтения · разбираем, набирать не нужно
	if len(stock) == 0 {
		fmt.Println("Ячеек нет")
		return
	}

return в main — ранний выход из дня 11.


Попробуйте сейчас: максимум и его ячейки.

Цель: программа из раздела выше работает в черновике, и вы видите, как меняется вывод от данных.

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

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

import "fmt"

func main() {
	stock := []int{12, 40, 5, 40, 7}
	sum := 0
	most := stock[0]
	for _, qty := range stock {
		sum += qty
		if qty > most {
			most = qty
		}
	}
	fmt.Println("Всего:", sum, "шт.")
	fmt.Print("Больше всего: ", most, " шт., ячейки:")
	for i, qty := range stock {
		if qty == most {
			fmt.Print(" ", i+1)
		}
	}
	fmt.Println()
}
▶ Выполните
cd ~/gocourse/day16/scratch
go run .

2. Замените в срезе последний элемент 7 на 40 и запустите снова: номеров ячеек с максимумом должно стать три.

3. Замените срез на пустой — stock := []int{} — и посмотрите на панику.

4. Добавьте проверку длины сразу после объявления среза — файл целиком:

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

import "fmt"

func main() {
	stock := []int{}
	if len(stock) == 0 {
		fmt.Println("Ячеек нет")
		return
	}
	sum := 0
	most := stock[0]
	for _, qty := range stock {
		sum += qty
		if qty > most {
			most = qty
		}
	}
	fmt.Println("Всего:", sum, "шт.")
	fmt.Print("Больше всего: ", most, " шт., ячейки:")
	for i, qty := range stock {
		if qty == most {
			fmt.Print(" ", i+1)
		}
	}
	fmt.Println()
}
▶ Выполните
go run .

Готово, когда: на пустом срезе программа печатает Ячеек нет и не падает.


Отбор по условию — ещё один срез

Задание дня просит перечислить ячейки ниже порога — а если таких нет, написать слово. Удобно сначала собрать номера в отдельный срез, как в трассировке на прошлой странице, а потом решить, что печатать: по len нового среза сразу видно, пуст ли список.

Код для чтения · разбираем, набирать не нужно
	low := []int{}
	for i, qty := range stock {
		if qty < limit {
			low = append(low, i+1)
		}
	}

Порог записывают один раз, константой из дня 02 — const limit = … над func main(), — а не числом в нескольких местах программы.


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

Цель: программа ~/gocourse/day16/stock/main.go печатает отчёт по ячейкам из ввода — порог у вас свой.

1. Прочитайте условие и пример:

▶ Выполните
cd ~/gocourse/day16/stock
cat TASK.txt
cat example.txt

2. Напишите main.go сами. Порядок работы: весь ввод в срез — программа со второй страницы; пустой ввод — проверка len; сумма и максимум — первый проход; ячейки с максимумом — второй; ячейки ниже порога — отбор в свой срез.

3. Проверьте на примере, на пустом вводе и на своих числах — в том числе на ячейке ровно на пороге:

▶ Выполните
go run . < example.txt
go run . < /dev/null
gofmt -l .

4. Закоммитьте:

▶ Выполните
cd ~/gocourse/day16
git add .
git commit -m "stock: отчёт по ячейкам"

Готово, когда: вывод на example.txt совпадает с примером из TASK.txt буква в букву — пункты stock и stocklimit. Проверка прогонит программу на скрытых вводах, а в копии испортит срез: отчёт, посчитанный по срезу, изменится, мимо среза — нет.


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

Что видите Что это значит Что делать
panic: runtime error: index out of range [0] with length 0 на пустом вводе обращение к stock[0] раньше проверки длины if len(…) == 0 — первым делом после чтения
ячейка ровно на пороге попала в список в условии <= «ниже порога» — строго меньше: <
номера ячеек на единицу меньше, первая — 0 печатается i, а не i+1 номер = индекс + 1
напечатан только первый номер с максимумом номера собираются в первом проходе, когда максимум ещё не окончательный номера — вторым проходом, после того как максимум найден
проверка пишет: сумма и максимум мимо среза отчёт считается на лету, в переменных сумма и максимум — циклом по срезу
между словом и числом лишний или пропавший пробел fmt.Print ставит пробел только между двумя нестроками сверьте пробелы с примером, пробел пишите внутри строки " "

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