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

Максимум и минимум

Другой вид накопителя хранит лучшее из виденного: самую крупную отгрузку, самую мелкую. Места те же — до цикла, в теле, после; ловушка в начальном значении.


Максимум: запомнить лучшее

Ситуация. Нужна самая крупная отгрузка за день.

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

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

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	best := 0
	line := 0
	for scanner.Scan() {
		qty, err := strconv.Atoi(strings.TrimSpace(scanner.Text()))
		if err != nil {
			fmt.Fprintln(os.Stderr, "Не число:", scanner.Text())
			os.Exit(1)
		}
		line++
		if qty > best {
			best = qty
		}
	}
	fmt.Println("Строк:", line, "самая крупная:", best)
}

Что увидите.

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day08/scratch$ go run . < day.txt
Строк: 4 самая крупная: 40

Как это читать. best — «самое крупное из прочитанного»: на каждом проходе новое значение сравнивается с ним и, если больше, становится новым best. Для day.txt (12, 40, 5, 40): 0 → 12 → 40 → 40 → 40.

Не называйте такую переменную max или min: это встроенные функции из дня 04, и переменная их закроет. Программа с одним import "fmt":

Код для чтения · разбираем, набирать не нужно
	max := 0
	for qty := 1; qty <= 3; qty++ {
		if qty > max {
			max = qty
		}
	}
	fmt.Println(max(max, 10))
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day08/scratch$ go run .
# day08/scratch
./main.go:12:14: invalid operation: cannot call max (variable of type int): int is not a function

Начальное значение

Ситуация. Та же программа, но нужна самая мелкая отгрузка: меняем знак и имя.

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

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

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	least := 0
	for scanner.Scan() {
		qty, err := strconv.Atoi(strings.TrimSpace(scanner.Text()))
		if err != nil {
			fmt.Fprintln(os.Stderr, "Не число:", scanner.Text())
			os.Exit(1)
		}
		if qty < least {
			least = qty
		}
	}
	fmt.Println("Самая мелкая:", least)
}
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day08/scratch$ go run . < day.txt
Самая мелкая: 0

Нулевой отгрузки в файле нет, а напечатан 0: ни одно из чисел 12, 40, 5, 40 не меньше нуля, и least ни разу не поменялся. Начальное значение участвует в сравнении как ещё одно число, которого во вводе не было. Надёжное правило: первое значение взять как есть, сравнивать со второго, а какое первое — подскажет номер прохода:

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

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

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	least := 0
	count := 0
	for scanner.Scan() {
		qty, err := strconv.Atoi(strings.TrimSpace(scanner.Text()))
		if err != nil {
			fmt.Fprintln(os.Stderr, "Не число:", scanner.Text())
			os.Exit(1)
		}
		if count == 0 || qty < least {
			least = qty
		}
		count++
	}
	if count == 0 {
		fmt.Println("Отгрузок не было")
	} else {
		fmt.Println("Самая мелкая:", least)
	}
}
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day08/scratch$ go run . < day.txt
Самая мелкая: 5
stagiaire@lab:~/gocourse/day08/scratch$ go run . < /dev/null
Отгрузок не было

count == 0 || qty < least — короткое вычисление из дня 06: на первом проходе левая часть истинна, правая не считается. Пустой ввод проверяют после цикла, как среднее на странице 6. У максимума с best := 0 та же ловушка на вводе из одних нулей, и правило «первое как есть» закрывает её.


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

Цель: своими запусками увидеть, как начальное значение портит минимум.

1. Наберите в scratch/main.go вторую программу — с «первое как есть»:

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

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

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	least := 0
	count := 0
	for scanner.Scan() {
		qty, err := strconv.Atoi(strings.TrimSpace(scanner.Text()))
		if err != nil {
			fmt.Fprintln(os.Stderr, "Не число:", scanner.Text())
			os.Exit(1)
		}
		if count == 0 || qty < least {
			least = qty
		}
		count++
	}
	if count == 0 {
		fmt.Println("Отгрузок не было")
	} else {
		fmt.Println("Самая мелкая:", least)
	}
}
▶ Выполните
cd ~/gocourse/day08/scratch
go run . < day.txt
go run . < /dev/null

2. Уберите из условия count == 0 ||, запустите на day.txt снова — вернулся ноль — и верните обратно.

3. Третий опыт, least := 1000 вместо правила «первое как есть», — в course extra 08.

Готово, когда: программа печатает Самая мелкая: 5 и `Отгрузок

не было, а без count == 0 || — Самая мелкая: 0`.


Первый или последний

Ситуация. Нужен и размер самой крупной отгрузки, и номер строки. В day.txt число 40 встречается дважды — какую строку назвать?

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

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

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	best := 0
	bestLine := 0
	line := 0
	for scanner.Scan() {
		qty, err := strconv.Atoi(strings.TrimSpace(scanner.Text()))
		if err != nil {
			fmt.Fprintln(os.Stderr, "Не число:", scanner.Text())
			os.Exit(1)
		}
		line++
		if line == 1 || qty > best {
			best = qty
			bestLine = line
		}
	}
	fmt.Println("Самая крупная:", best, "в строке", bestLine)
}
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day08/scratch$ go run . < day.txt
Самая крупная: 40 в строке 2

С qty >= best вместо qty > best:

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day08/scratch$ go run . < day.txt
Самая крупная: 40 в строке 4

Строгое > не заменяет best равным значением — запоминается первое из равных; нестрогое >= заменяет — последнее. Какое нужно, говорит условие задачи, а проверить это можно только вводом, где максимум встречается дважды.


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

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

1. Сначала на бумаге: для каждого дня выпишите qty, сработало ли условие, и best, bestDay после прохода.

Код для чтения · разбираем, набирать не нужно
	best := 0
	bestDay := 0
	for day := 1; day <= 4; day++ {
		qty := day * (5 - day)
		if qty >= best {
			best = qty
			bestDay = day
		}
	}
	fmt.Println(best, bestDay)

2. Сдать два числа через пробел — best и bestDay:

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

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

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

import "fmt"

func main() {
	best := 0
	bestDay := 0
	for day := 1; day <= 4; day++ {
		qty := day * (5 - day)
		if qty >= best {
			best = qty
			bestDay = day
		}
	}
	fmt.Println(best, bestDay)
}
▶ Выполните
cd ~/gocourse/day08/scratch
go run .

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


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

Что видите Что это значит Что делать
минимум 0, хотя нулей во вводе нет начальное значение участвует в сравнении первое взять как есть: условие с count == 0 и «или»
назван номер последней из равных строк стоит >= для первой из равных — >
cannot call max (variable of type int): int is not a function переменная max закрыла встроенную функцию другое имя: best, least

Словарик ошибок

К концу дня в ~/errors.md — минимум шестнадцать разборов: вчерашние и сегодняшние. Строка засчитывается, если в ней текст ошибки, тире с пробелами вокруг и что это значило. Сегодняшние собраны в таблицах «Что может пойти не так» на всех семи страницах.


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

Цель: словарик пополнен, course check 08 показывает, что осталось.

1. Допишите в ~/errors.md сегодняшние ошибки и выполните:

▶ Выполните
course check 08

2. Звёздочка, в зачёт не входит: задание biggest.

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

Неудобные вводы к нему придумайте сами и проверьте через

printf '…' | go run ..

Готово, когда: в course check 08 пункт errors зелёный и зачтено не меньше 14 пунктов из 16.


Материал дня закончен. Задания — course lab 08, дополнительное чтение — course extra 08