День 08 · Циклы: for, границы, накопители · страница 2 из 7

Границы: сколько раз выполнится тело

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


Четыре заголовка — четыре ответа

Ситуация. В условии «ячейки с 1 по n». Сколько проходов даст цикл, зависит от двух мелочей: с чего начать и какой знак.

Код для чтения · разбираем, набирать не нужно
	n := 3
	for i := 0; i < n; i++ {
		fmt.Print(i, " ")
	}
	fmt.Println("— i := 0; i < n")
	for i := 1; i <= n; i++ {
		fmt.Print(i, " ")
	}
	fmt.Println("— i := 1; i <= n")
	for i := 1; i < n; i++ {
		fmt.Print(i, " ")
	}
	fmt.Println("— i := 1; i < n")
	for i := 0; i <= n; i++ {
		fmt.Print(i, " ")
	}
	fmt.Println("— i := 0; i <= n")

Что увидите.

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day08/scratch$ go run .
0 1 2 — i := 0; i < n
1 2 3 — i := 1; i <= n
1 2 — i := 1; i < n
0 1 2 3 — i := 0; i <= n

fmt.Print(i, " ") печатает число и пробел, не переводя строку; переводит его Println после цикла.

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

Заголовок Значения i Сколько проходов
i := 0; i < n от 0 до n − 1 n
i := 1; i <= n от 1 до n n
i := 1; i < n от 1 до n − 1 n − 1
i := 0; i <= n от 0 до n n + 1

Со знаком < последнее значение не входит, со <= — входит. Отсюда подсчёт: для < проходов «конец минус начало», для <= — на один больше. Первые две формы дают ровно n проходов: «с нуля и <» считает позиции, «с единицы и <=» — номера, как у людей. Смешанные формы — обычно описка. Та же программа с n := 5 — в course extra 08.


Ошибка на единицу

Ячейки с 101 по 105 — это не 105 − 101 = 4 ячейки, а пять: как столбов у забора, где первый на нуле.

Код для чтения · разбираем, набирать не нужно
	first := 101
	last := 105
	for cell := first; cell <= last; cell++ {
		fmt.Println("Ячейка", cell)
	}
	fmt.Println("Ячеек:", last-first+1)
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day08/scratch$ go run .
Ячейка 101
Ячейка 102
Ячейка 103
Ячейка 104
Ячейка 105
Ячеек: 5

Промах на один проход называют ошибкой на единицу (off-by-one): самый частый дефект в циклах. Ловят его тремя вопросами к заголовку:

  1. Какое первое значение получит переменная?
  2. Какое последнее пройдёт проверку?
  3. Сколько проходов всего — сходится ли с условием задачи?

И запуском на крайних данных: одна ячейка (first равно last), ни одной, диапазон на границе.


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

Цель: fix1 печатает все ячейки; правка — одна-две строки.

Он работает, но теряет одну ячейку; номера ячеек у каждого свои.

1. Запустите:

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

2. Сравните вывод с комментарием в начале файла и задайте заголовку три вопроса выше. Больше двух изменённых строк проверка не примет; first и last не меняйте: чинится граница, а не данные, и «Ячейка» должна печататься одной строкой — той, что внутри цикла.

3. Посмотрите свою правку:

▶ Выполните
diff ~/.course/orig/day08/fix1/main.go main.go

Готово, когда: вывод идёт от first до last включительно и кончается строкой «Всего ячеек: …», а diff показывает одну-две строки — пункты fix1 и t_diff.


Шаг и обратный отсчёт

Третья часть не обязана прибавлять единицу: доставка раз в неделю — шаг семь, отсчёт до отправки машин — шаг назад.

Код для чтения · разбираем, набирать не нужно
	for day := 1; day <= 30; day += 7 {
		fmt.Print(day, " ")
	}
	fmt.Println()
	for left := 3; left > 0; left-- {
		fmt.Println("До отправки машин:", left)
	}
	fmt.Println("Отправка")
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day08/scratch$ go run .
1 8 15 22 29 
До отправки машин: 3
До отправки машин: 2
До отправки машин: 1
Отправка

fmt.Println() без аргументов только переводит строку. С шагом 7 число 30 не встретилось: после 29 идёт 36, и цикл кончился.

Бывает и так, что тело не выполняется ни разу: у for i := 5; i < 3; i++ первая же проверка ложна. Это не ошибка — пустой диапазон нормальный случай, и на нём программа должна вести себя разумно.


Попробуйте сейчас: сколько раз выполнится тело.

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

1. Сколько раз выполнится тело?

Код для чтения · разбираем, набирать не нужно
	count := 0
	for i := 10; i >= 0; i -= 2 {
		count++
	}
	fmt.Println(count)

Выпишите значения i, при которых условие истинно, — с первым и последним.

2. Сдать число (вместо ЧИСЛО — сколько раз выполнится тело):

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

3. Потом проверьте запуском — программа целиком:

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

import "fmt"

func main() {
	count := 0
	for i := 10; i >= 0; i -= 2 {
		count++
	}
	fmt.Println(count)
}
▶ Выполните
cd ~/gocourse/day08/scratch
go run .

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


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

Что видите Что это значит Что делать
не хватает последней строки или есть лишняя ошибка на единицу: < вместо <=, начало с 0 вместо 1 три вопроса к заголовку
итог «Всего» на один меньше или больше счётчик прошёл столько раз, сколько тело чинить заголовок, а не итог
последнее значение с шагом не то шаг перепрыгнул границу: 29 + 7 > 30 проверить, какое значение последним проходит проверку

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

Цель: pack/main.go печатает таблицу упаковки по условию TASK.txt.

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

▶ Выполните
cd ~/gocourse/day08/pack
cat TASK.txt

2. Напишите программу в main.go: три числа — тремя вызовами scanner.Scan(), как в день 07, таблица — один цикл от «от» до «до» включительно. Прежде чем писать заголовок, ответьте на три вопроса.

3. Проверить себя:

▶ Выполните
printf '6\n10\n13\n' | go run .

4. Потом неудобные вводы: «от» равно «до», «от» больше «до», партия ровно на целые коробки. Проверка прогонит и скрытые.

Готово, когда: на вводе 6, 10, 13 вывод совпадает с примером в TASK.txt, а gofmt -l . молчит — пункты pack и code_fmt (второй — вместе с total).


Дальше: цикл с условием, break и continue — course next