Накопители: сумма, количество, среднее
Цикл видит одну строку за раз, а итог дня нужен после последней, когда строк уже нет. Значит, по ходу цикла что-то должно накапливаться.
Накопитель живёт до цикла
Ситуация. В scratch/day.txt отгрузки за день, по числу в строке; нужны число отгрузок, сумма и среднее.
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
total := 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)
}
total += qty
count++
}
fmt.Println("Отгрузок:", count)
fmt.Println("Всего, шт.:", total)
fmt.Printf("В среднем: %.2f\n", float64(total)/float64(count))
}Что увидите.
stagiaire@lab:~/gocourse/day08/scratch$ go run . < day.txt
Отгрузок: 4
Всего, шт.: 97
В среднем: 24.25Как это читать. Накопитель — переменная с тремя местами:
| Где | Сумма total |
Количество count |
|---|---|---|
| до цикла — начальное значение | total := 0 |
count := 0 |
| в теле — на каждом проходе | total += qty |
count++ |
| после цикла — результат | печать | печать |
Начальное значение — то, что должно получиться, если строк нет: сумма 0, количество 0. Среднее — не накопитель: его считают один раз после цикла.
float64(total)/float64(count) — дробное деление из дня 03. Поделите целые — и дробная часть пропадёт до преобразования (программа с одним import "fmt"):
total := 57
count := 4
fmt.Println(total / count)
fmt.Println(float64(total / count))
fmt.Printf("%.2f\n", float64(total)/float64(count))stagiaire@lab:~/gocourse/day08/scratch$ go run .
14
14
14.25Попробуйте сейчас: итог дня на двух файлах.
Цель: программа итога работает на
day.txt, и вы знаете, чего ей не хватает для пустых строк.1. Наберите её в
scratch/main.go:✎ Наберите в файлscratch/main.gopackage main import ( "bufio" "fmt" "os" "strconv" "strings" ) func main() { scanner := bufio.NewScanner(os.Stdin) total := 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) } total += qty count++ } fmt.Println("Отгрузок:", count) fmt.Println("Всего, шт.:", total) fmt.Printf("В среднем: %.2f\n", float64(total)/float64(count)) }Запустите с
day.txt:▶ Выполнитеcd ~/gocourse/day08/scratch go run . < day.txt2. Потом с
lines.txt— там вторая строка пустая:▶ Выполнитеgo run . < lines.txtГотово, когда: с
day.txtвывод такой же, как выше, а проlines.txtвы можете сказать, что случилось и какой строки не хватает.
Накопитель внутри тела
Ситуация. Объявлять переменную рядом с тем местом, где она меняется, — частая привычка; здесь она ломает счёт.
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
total := 0
qty, err := strconv.Atoi(strings.TrimSpace(scanner.Text()))
if err != nil {
fmt.Fprintln(os.Stderr, "Не число:", scanner.Text())
os.Exit(1)
}
total += qty
fmt.Println("Всего, шт.:", total)
}
}stagiaire@lab:~/gocourse/day08/scratch$ go run . < day.txt
Всего, шт.: 12
Всего, шт.: 40
Всего, шт.: 5
Всего, шт.: 40Как это читать. total := 0 стоит в теле и выполняется на каждом проходе: новая ячейка с нулём, к ней одна отгрузка. Программа при этом собирается: total объявлена и прочитана в total += qty, придраться нечему. Перенесите печать после цикла — компилятор отзовётся, но не о причине, а о следствии:
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
total := 0
qty, err := strconv.Atoi(strings.TrimSpace(scanner.Text()))
if err != nil {
fmt.Fprintln(os.Stderr, "Не число:", scanner.Text())
os.Exit(1)
}
total += qty
}
fmt.Println("Всего, шт.:", total)
}stagiaire@lab:~/gocourse/day08/scratch$ go run . < day.txt
# day08/scratch
./main.go:22:36: undefined: totalПравило то же, что у переменной цикла: объявленное в теле живёт до его }. Лечение одно — объявлять накопитель до цикла.
Попробуйте сейчас: починить накопитель в fix2.
Цель:
fix2печатает верный нарастающий итог; правка — одна-две строки, больше двух проверка не примет.1. Запустите:
▶ Выполнитеcd ~/gocourse/day08/fix2 go run . < example.txt2. Сравните с комментарием в начале файла и протрассируйте в голове два первых прохода: где создаётся
totalи что в ней лежит перед+=?Готово, когда:
go run . < example.txtпечатает ровно те три строки, что в комментарии, аdiffс оригиналом — одну-две изменённые строки (пунктfix2).
Пустой ввод: делить не на что
Та же программа на пустом вводе (/dev/null — файл из нуля строк).
stagiaire@lab:~/gocourse/day08/scratch$ go run . < /dev/null
Отгрузок: 0
Всего, шт.: 0
В среднем: NaNNaN — not a number, «не число»: так %.2f печатает дробный ноль, делённый на ноль. А с целым делением total/count будет паника:
stagiaire@lab:~/gocourse/day08/scratch$ go run . < /dev/null
panic: runtime error: integer divide by zero
goroutine 1 [running]:
main.main()
/home/stagiaire/gocourse/day08/scratch/main.go:24 +0x215
exit status 2Путь в четвёртой строке — ваш домашний каталог, а число после + зависит от сборки: у вас будут свои. Важны первая строка и номер строки в main.go. Пустой ввод проверяют после цикла, до деления: if count == 0 { … } else { … }.
Что может пойти не так
| Что видите | Что это значит | Что делать |
|---|---|---|
| «итог» равен последнему значению | накопитель объявлен в теле и обнуляется каждый проход | объявлять до цикла |
undefined: total после цикла |
он объявлен в теле, дальше его нет | объявление — до цикла |
В среднем: NaN или panic: runtime error: integer divide by zero |
деление при count = 0, дробное или целое |
проверить count == 0 до деления |
| среднее без дробной части: 14 вместо 14.25 | сначала целое деление, потом float64 |
преобразовать оба числа до деления |
| отгрузок на единицу больше, чем чисел | count++ до continue: считает пустые строки |
счётчик — после всех проверок |
Попробуйте сейчас: задание total — итог дня.
Цель:
total/main.goпечатает итог дня поTASK.txt.1. Прочитайте условие:
▶ Выполнитеcd ~/gocourse/day08/total cat TASK.txt2. Напишите программу в
main.go. Счётчиков здесь два: номер строки во вводе (для сообщения об ошибке) и число отгрузок (для итога), и растут они в разных местах тела — решите где, прежде чем писать.3. Проверить себя на примерах из условия:
▶ Выполнитеgo run . < example1.txt go run . < example2.txt go run . < /dev/nullСообщение об ошибке идёт в поток ошибок, и программа выходит с кодом 1: проверка смотрит оба потока по отдельности.
Готово, когда: на
example1.txtиexample2.txtвывод совпадает с примерами вTASK.txt, на/dev/null— одна строкаОтгрузок не было, аgofmt -l .ничего не печатает — пунктыtotalиcode_fmt.
Дальше: максимум и минимум — course next