Пишем по кусочкам
Ситуация. Для задачи об упаковке заказа шаги 1–3 сделаны: условие прочитано, примеры посчитаны, подцели названы. Нужна программа, которая на всех трёх примерах печатает то же, что таблица. Написать её целиком и запустить один раз — значит, получить неверное число и не знать, в какой из десяти строк ошибка. Поэтому шаг 4 — код кусками. Правило одно: пара строк — запуск — сверка с таблицей примеров.
Эту страницу достаточно прочитать: весь разбор с настоящим выводом — ниже, а приглашение ~/gocourse/day05/pack$ в примерах говорит, где он снят. Повторить разбор руками — задание со звёздочкой в конце страницы: тогда программа целиком живёт в папке ~/gocourse/day05/pack, и проверка её не сверяет.
Кусок 1: данные
Начинаем с переменных из условия и первой строки вывода:
package main
import "fmt"
func main() {
items := 50
perBox := 12
boxPrice := 90
fmt.Println("Штук в заказе:", items)
}stagiaire@lab:~/gocourse/day05/pack$ go run .
# day05/pack
./main.go:7:2: declared and not used: perBox
./main.go:8:2: declared and not used: boxPriceПервая ловушка метода «кусками» — именно в Go: компилятор не собирает программу с переменной, которая нигде не используется (день 02). В законченной программе это полезно — лишняя переменная обычно означает забытый расчёт. В недописанной мешает.
Выхода два. Объявлять переменную, когда до неё дошла очередь. Или временно напечатать все данные отдельной строкой — заодно видно, что они такие, как задумано.
Дальше показываем только строки из тела main — вставляйте их между { и } в scratch/main.go. С одним import "fmt" первая строка тела — шестая. В разборе ниже их место — в конце main программы из куска 1, перед }. То есть отдельный фрагмент пробуют в scratch/main.go, а программу целиком, если повторяете разбор руками, держат в pack.
fmt.Println("данные:", items, perBox, boxPrice)
fmt.Println("Штук в заказе:", items)stagiaire@lab:~/gocourse/day05/pack$ go run .
данные: 50 12 90
Штук в заказе: 50Строка данные: временная, в конце её уберём.
Кусок 2: коробки
Первая мысль — поделить:
boxes := items / perBox
fmt.Println("Коробок:", boxes)stagiaire@lab:~/gocourse/day05/pack$ go run .
данные: 50 12 90
Штук в заказе: 50
Коробок: 4Сверка с таблицей: нужно 5. Деление целых отбрасывает остаток (день 03), и две штуки остались без коробки. Добавим коробку:
boxes := items/perBox + 1stagiaire@lab:~/gocourse/day05/pack$ go run .
данные: 50 12 90
Штук в заказе: 50
Коробок: 5С первым примером совпало, и здесь обычно останавливаются. Меняем items := 50 на items := 48 — второй пример:
stagiaire@lab:~/gocourse/day05/pack$ go run .
данные: 48 12 90
Штук в заказе: 48
Коробок: 5По таблице нужно 4. + 1 добавляет коробку всегда, даже когда остатка нет. Без неудобного примера эта ошибка дожила бы до склада.
Нужна запись «разделить и округлить вверх» на целых числах:
boxes := (items + perBox - 1) / perBoxКак это читать. К числу штук добавляем почти полную коробку — на одну штуку меньше полной. Если остаток был, сумма переходит через границу следующей коробки, и деление даёт на одну больше. Если остатка не было, perBox - 1 до следующей границы не дотягивает, и результат прежний:
| items | items + perBox − 1 | ÷ 12, дробь отброшена |
|---|---|---|
| 50 | 61 | 5 |
| 48 | 59 | 4 |
| 7 | 18 | 1 |
stagiaire@lab:~/gocourse/day05/pack$ go run .
данные: 48 12 90
Штук в заказе: 48
Коробок: 4С items := 7:
stagiaire@lab:~/gocourse/day05/pack$ go run .
данные: 7 12 90
Штук в заказе: 7
Коробок: 1Тот же результат дала бы math.Ceil из дня 04, через дробные числа: int(math.Ceil(float64(items) / float64(perBox))) и import "math". Целочисленная запись короче и обходится без дробей.
Кусок 3: последняя коробка
Снова первая мысль — остаток от деления:
last := items % perBox
fmt.Println("В последней коробке:", last, "шт.")С items := 48:
stagiaire@lab:~/gocourse/day05/pack$ go run .
данные: 48 12 90
Штук в заказе: 48
Коробок: 4
В последней коробке: 0 шт.Коробок с нулём штук не бывает: по таблице в последней 12. Остаток годится, только когда он есть. Считаем иначе: все коробки, кроме последней, полные, а последней достаётся то, что осталось:
last := items - (boxes-1)*perBoxstagiaire@lab:~/gocourse/day05/pack$ go run .
данные: 48 12 90
Штук в заказе: 48
Коробок: 4
В последней коробке: 12 шт.Для 50: 50 − 4 × 12 = 2. Для 7: 7 − 0 × 12 = 7. Совпадает со всей таблицей.
Кусок 4: стоимость и итог
Стоимость — в копейках, печать в рублях через %d.%02d, как в день 03. Временную строку данные: убираем, комментарии дописываем. Вся программа:
package main
import "fmt"
func main() {
items := 50 // штук в заказе
perBox := 12 // штук в одной коробке
boxPrice := 90 // упаковка одной коробки, копеек
boxes := (items + perBox - 1) / perBox
last := items - (boxes-1)*perBox
cost := boxes * boxPrice
fmt.Println("Штук в заказе:", items)
fmt.Println("Коробок:", boxes)
fmt.Println("В последней коробке:", last, "шт.")
fmt.Printf("Упаковка: %d.%02d руб.\n", cost/100, cost%100)
}Как это читать. Три части, разделённые пустыми строками: данные, расчёт, печать. Числа из условия записаны один раз — в начале. Расчёт и печать пользуются только именами. Поэтому проверить другой пример — значит поменять одно число в первой строке, и больше ничего.
Шаг 5: сверить
Примеры по очереди — 50, 48 и 7:
stagiaire@lab:~/gocourse/day05/pack$ go run .
Штук в заказе: 50
Коробок: 5
В последней коробке: 2 шт.
Упаковка: 4.50 руб.stagiaire@lab:~/gocourse/day05/pack$ go run .
Штук в заказе: 48
Коробок: 4
В последней коробке: 12 шт.
Упаковка: 3.60 руб.stagiaire@lab:~/gocourse/day05/pack$ go run .
Штук в заказе: 7
Коробок: 1
В последней коробке: 7 шт.
Упаковка: 0.90 руб.Каждая строка совпала с таблицей. Осталось проверить формат и подозрительные места:
stagiaire@lab:~/gocourse/day05/pack$ gofmt -l .
stagiaire@lab:~/gocourse/day05/pack$ go vet .Обе команды молчат — это успех. go vet ловит то, что пропускает компилятор, например %d для дробного числа в Printf.
И последнее: верните в первой строке значение из условия. Сдаётся программа с данными условия, а не с последним примером, который вы пробовали.
Что может пойти не так
| Что видите | Что это значит | Что делать |
|---|---|---|
declared and not used: perBox на первом запуске |
переменная объявлена раньше, чем понадобилась | временная строка fmt.Println("данные:", ...) или объявлять позже |
| с одним примером совпало, с другим нет | формула верна только для удобных данных | не подгонять под пример, а понять, чем примеры отличаются: есть ли остаток, не ноль ли |
Упаковка: 1.5 руб. при стоимости 105 копеек |
копейки напечатаны %d, а не %02d |
%d.%02d |
| после правки печатается старое | файл не сохранён | Ctrl+S |
в выводе осталась строка данные: ... |
временная печать не убрана | удалить перед сдачей |
числа из условия вписаны прямо в Println |
программа верна только для одного набора данных | числа — один раз в переменных в начале main, дальше только имена |
Попробуйте сейчас: разминка warmup.
Цель:
warmup/main.goрешает задачу изTASK.txtи проходит оба запуска проверки.Первая задача, которую сверит
course check 05. В контрольную точку она не входит, но устроена так же, как её задачи на написание программ.▶ Выполнитеcd ~/gocourse/day05/warmup cat TASK.txtВ условии — пример с ответом и ваши данные без ответа. Пройдите пять шагов: посчитайте ответ для своих данных на бумаге, назовите подцели, пишите
main.goкусками. Файлаmain.goв папке нет — создайте его.Первые строки
main— ровно такие, как в разделе «ВАШИ ДАННЫЕ». Проверка запускает программу дважды: как есть и ещё раз, подставив в эти строки данные примера. Поэтому правило «числа из условия — один раз, в начале» здесь обязательное: число, посчитанное в уме и вписанное вPrintln, второй запуск не пройдёт.Когда вывод совпал с посчитанным на бумаге — шаг 5, сверка. Все три команды проверка ищет в журнале:
▶ Выполнитеgo run . gofmt -l . go vet .Готово, когда: вывод
go run .совпал с ответом, посчитанным на бумаге для ваших данных, аgofmt -l .иgo vet .ничего не напечатали (пункты проверкиwarmup,t_gorun,t_gofmt,t_vet).
Со звёздочкой, в зачёт не входит. Повторите разбор в ~/gocourse/day05/pack — кусками, с запусками, как на странице. Прогоните четыре примера: три из таблицы и 25 штук с прошлой страницы — совпал ли ваш ответ на бумаге?
Дальше: как читать чужую программу — course next