JavaRush /Курсы /Go SELF /Шаблон read → parse → compute → print

Шаблон read → parse → compute → print

Go SELF
2 уровень , 5 лекция
Открыта

1. Введение

Когда вы только начинаете решать задачи, очень легко превратить main() в кашу: половина строк читает ввод, четверть что-то считает, ещё немного печатает, а посередине внезапно конкатенация строк и попытка умножить текст на число (спойлер: Go не оценит). Шаблон readparsecomputeprint нужен, чтобы ваш код был предсказуемым: вы сами всегда знаете, «на каком этапе» вы сейчас, и где искать ошибку, если результат не тот.

Самое приятное в этом шаблоне — он подходит почти ко всем задачам со стандартным вводом/выводом. Даже если задача простая, привычка «разложить по этапам» экономит нервы. А нервы, как известно, в программировании — расходник, но лучше, чтобы расход был умеренным.

Общая схема: что означает каждый шаг

Если описать шаблон человеческим языком, то получится буквально «прочитал → понял → посчитал → сказал результат». Звучит как обычный разговор, и это хорошо: программирование для новичка должно быть максимально «разговорным», без ощущения, что вы вызываете древних духов.

Ниже — схема, которую полезно держать в голове (и иногда прямо писать комментариями в коде):

flowchart LR
    A[read: читаем вход] --> B[parse: при необходимости превращаем типы]
    B --> C[compute: считаем результат]
    C --> D[print: печатаем строго как нужно]

И небольшая таблица «что обычно лежит на каждом этапе»:

Этап Что делаем Типичный результат этапа
read
читаем данные в переменные a, b, s, n получили значения
parse
преобразуем строку в число (или наоборот) n := atoi(s) или text := itoa(n)
compute
выполняем вычисления sum, area, change, result
print
печатаем в нужном формате одна строка/одно число/несколько значений

Важно: шаг parse не всегда нужен. Если вход уже число, и мы читаем его в int, то парсить нечего. Но если число приходит «как текст», тогда без Atoi не обойтись.

2. Шаг read: читаем ввод так, чтобы дальше было удобно

На этапе read мы занимаемся самой типичной работой: объявили переменные — прочитали в них значения. Здесь часто возникает соблазн «сразу что-то посчитать прямо внутри Scan», но лучше не спешить: сначала достанем данные, потом будем думать.

Самый простой вариант — читаем числа сразу в int:

package main

import "fmt"

func main() {
	var a, b int
	fmt.Scan(&a, &b)     // read
	fmt.Println(a + b)   // compute+print (пока в одну строку)
}

Этот пример рабочий, но в нём этапы слеплены. Для тренировки шаблона лучше разделять, даже если кажется «да ладно, тут же 2 строчки».

Вот та же мысль, но аккуратнее:

package main

import "fmt"

func main() {
	// read
	var a, b int
	fmt.Scan(&a, &b)

	// compute
	sum := a + b

	// print
	fmt.Println(sum)
}

Пустые строки и комментарии здесь — не украшение. Это визуальные границы: вы потом будете благодарны себе, когда начнёте ошибаться в более длинных задачах (то есть примерно завтра… ой, то есть «в какой-то момент»).

3. Шаг parse: когда вход текстовый, а считать надо числа

На этапе parse мы превращаем один тип данных в другой. И тут важно принять философию Go: «само не преобразуется». Это поначалу раздражает, но потом внезапно становится понятно, почему так безопаснее: вы не можете случайно сложить “12” и 3 и получить “123” или 15 — вы обязаны выбрать явно.

Допустим, по условию задачи вам дают число как строку (это встречается чаще, чем кажется: коды, номера, иногда “0012” и т.д.). Тогда read будет читать строку, а parse — превращать её в int:

package main

import (
	"fmt"
	"strconv"
)

func main() {
	// read
	var s string
	fmt.Scan(&s)

	// parse
	n, _ := strconv.Atoi(s)

	// compute
	double := n * 2

	// print
	fmt.Println(double)
}

Здесь мы использовали _, чтобы игнорировать ошибку. В учебных задачах это иногда допустимо, потому что формат ввода гарантирован: если написано «на вход подаётся целое число», значит подаётся. Но важно понимать: Atoi возвращает (int, error), и error — это обычное значение, которое можно проверять и обрабатывать. В Go принято явно работать с ошибками как со значениями.

Если вы хотите хотя бы увидеть, была ли ошибка (без ветвлений, которые мы пока не используем), можно просто распечатать err:

package main

import (
	"fmt"
	"strconv"
)

func main() {
	var s string
	fmt.Scan(&s)

	n, err := strconv.Atoi(s)
	fmt.Println(n)    // если ошибка, n будет 0
	fmt.Println(err)  // <nil> если всё ок
}

Да, это не «красивая обработка», но для понимания механики — отлично.

4. Шаг compute: считаем результат и не путаем ответственность строк

Этап compute — место, где вы решаете задачу. Тут очень полезно держаться правила: «одна переменная — одна идея». Новички часто делают огромную формулу прямо внутри Println, а потом теряются, где ошибка: в арифметике, в приоритете операций или вообще в том, что ввод был не тем.

Вместо этого лучше так: промежуточные значения — в переменные с нормальными именами.

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

package main

import "fmt"

func main() {
	// read
	var width, height int
	fmt.Scan(&width, &height)

	// compute
	area := width * height
	perimeter := 2*width + 2*height

	// print
	fmt.Println(area, perimeter)
}

Обратите внимание: perimeter := 2*width + 2*height читается легко, потому что мы не пытаемся сделать «матан на время». Если хочется ещё яснее — можно добавить скобки, даже если они не нужны (иногда это нормальная плата за читаемость).

5. Шаг print: вывод — это часть решения, а не “ну потом как-нибудь”

Вывод в учебных задачах — штука коварная. Можно идеально посчитать ответ, но вывести “Ответ: 42” вместо “42” — и получить неправильное решение. Программа не обязана быть вежливой. Ей надо быть точной.

На этапе print полезно помнить несколько вещей.

fmt.Println печатает аргументы через пробел и добавляет перевод строки. fmt.Print печатает без перевода строки. Если вы выводите несколько чисел, Println — самый простой путь.

package main

import "fmt"

func main() {
	x := 5
	y := 7
	fmt.Println(x, y) // 5 7
}

Если вам нужно собрать строку руками (например, требуется “x=5”), тогда либо печатайте аргументами, либо используйте strconv.Itoa. Конкатенация строк с числами напрямую запрещена.

package main

import (
	"fmt"
	"strconv"
)

func main() {
	x := 5
	line := "x=" + strconv.Itoa(x)
	fmt.Println(line) // x=5
}

6. Мини‑пример: «Касса»

Сейчас соберём маленькое единое приложение, которое идеально ложится на шаблон: мини‑«касса». Идея простая: на вход подаётся стоимость и сумма оплаты (в центах), на выход — сдача, тоже в центах, а затем разложенная на доллары и центы. Условия мы пока не используем, поэтому считаем, что оплата всегда не меньше стоимости.

Начнём с версии, где вход — уже числа (int). Это прямолинейно и приятно.

package main

import "fmt"

func main() {
	// read
	var priceCents, paidCents int
	fmt.Scan(&priceCents, &paidCents)

	// compute
	changeCents := paidCents - priceCents
	changeDollars := changeCents / 100
	changeRestCents := changeCents % 100

	// print
	fmt.Println(changeDollars, changeRestCents)
}

Теперь усложним жизнь (чуть-чуть): пусть цена и оплата приходят как строки. Это распространённая ситуация, когда данные «похожи на число», но лежат в текстовом виде.

package main

import (
	"fmt"
	"strconv"
)

func main() {
	// read
	var priceStr, paidStr string
	fmt.Scan(&priceStr, &paidStr)

	// parse
	priceCents, _ := strconv.Atoi(priceStr)
	paidCents, _ := strconv.Atoi(paidStr)

	// compute
	changeCents := paidCents - priceCents
	dollars := changeCents / 100
	cents := changeCents % 100

	// print
	fmt.Println(dollars, cents)
}

Заметьте, насколько предсказуемо это читается: вы буквально глазами видите, где ввод, где парсинг, где математика, где вывод. Это и есть главная ценность шаблона.

7. Как называть переменные, чтобы не путаться

Хорошие имена переменных — это когда вы можете прочитать код как историю. Плохие — когда код превращается в “x1, x2, tmp, res2”, и вы начинаете гадать: tmp — это временно или навсегда? а почему res2, где res1? а x — это ширина или возраст кота?

Соглашение, которое хорошо работает в задачах со вводом/выводом: входные переменные называем по смыслу (что это такое), а выходные — по роли (что это за результат).

Например, сравните два варианта. Формально оба работают, но мозгу от них по-разному.

package main

import "fmt"

func main() {
	var a, b int
	fmt.Scan(&a, &b)
	c := a*b + a + b
	fmt.Println(c)
}

А теперь так:

package main

import "fmt"

func main() {
	var width, height int
	fmt.Scan(&width, &height)

	area := width * height
	perimeter := 2*width + 2*height

	fmt.Println(area, perimeter)
}

Во втором случае даже человек, который не писал эту программу, понимает, что происходит. И это важнее, чем кажется: вы сами через пару часов уже «не тот человек, который писал код» (память любит отпуск).

Ниже маленькая подсказка-таблица, как часто удобно называть переменные в учебных задачах:

Смысл Примеры имён
размеры
width, height, length
количество
n, count, total
сумма
sum, totalSum
ответ
result, answer
строка входа
s, text, priceStr
число после парса
n, value, priceCents

Обратите внимание: иногда n — нормальное имя. Но только когда оно реально означает “количество” или “размер”, а не «я устал придумывать названия».

8. Микро-диагностика: печать промежуточных значений

Когда вы решаете задачу и получаете «не тот ответ», очень хочется сразу впасть в мистику: “компьютер сломался”, “в Go баг”, “вселенная против меня”. На практике чаще всего проблема в одном из этапов: не так прочитали, не так распарсили, не так посчитали или не так вывели.

Пока мы не используем условия, лучший способ отладки — временно печатать промежуточные значения. Причём печатать так, чтобы потом легко удалить.

Например, в «кассе»:

package main

import "fmt"

func main() {
	var priceCents, paidCents int
	fmt.Scan(&priceCents, &paidCents)

	changeCents := paidCents - priceCents

	fmt.Println(priceCents, paidCents, changeCents) // временная отладка

	dollars := changeCents / 100
	cents := changeCents % 100
	fmt.Println(dollars, cents)
}

Да, это ломает формат вывода для платформы. Но отладка и «финальная версия» — разные состояния кода. Важно просто не забывать убрать отладочные строки перед сдачей (это вечная классика).

9. Типичные ошибки

Ошибка №1: смешивание этапов до состояния “непонятно где что”.
Часто начинающий пишет: fmt.Scan(&a); fmt.Println("ans", a+something) — и так по всей программе. Вроде работает, пока задача маленькая. Но как только появляется парсинг строки, несколько входов или пара промежуточных вычислений, код превращается в клубок. Лечится дисциплиной: сначала read, потом (если нужно) parse, потом compute, потом print, и между ними — пустые строки.

Ошибка №2: попытка “склеить строку и число” оператором +.
В Go оператор + либо складывает числа, либо склеивает строки. Он не делает «и то, и другое по настроению». Поэтому "age=" + age не скомпилируется. Если нужно вывести вместе, печатайте аргументами (fmt.Println("age=", age)) или превращайте число в строку через strconv.Itoa.

Ошибка №3: парсинг делается “на всякий случай”, даже когда не нужен.
Иногда студент уже узнал Atoi и начинает использовать его всегда: читает int через Scan, а потом думает “а не распарсить ли ещё раз”. Это лишнее и путает. Если вход — число, читайте в int и сразу считайте. parse нужен только когда тип входа не совпадает с типом, с которым вы хотите работать.

Ошибка №4: имена переменных вида x, x1, x2, tmp, когда смысл важен.
Короткие имена допустимы, но не должны превращать задачу в шифровку. Если величина — ширина, пусть будет width. Если это стоимость в центах, пусть будет priceCents. Такие имена уменьшают количество логических ошибок: вы реже перепутаете местами величины, потому что они выглядят по-разному.

Ошибка №5: неправильный вывод из-за “дружелюбных слов”.
Очень хочется писать fmt.Println("Answer:", result) — программа выглядит умнее. Но проверяющая система обычно ждёт строго result, без префиксов. Поэтому на этапе print держите в голове правило: выводим только то, что просит условие, и ровно в том формате, который просит условие.

1
Задача
Go SELF, 2 уровень, 5 лекция
Недоступна
Страницы книг
Страницы книг
1
Задача
Go SELF, 2 уровень, 5 лекция
Недоступна
Утроитель кода
Утроитель кода
1
Задача
Go SELF, 2 уровень, 5 лекция
Недоступна
Сдача кассы
Сдача кассы
1
Задача
Go SELF, 2 уровень, 5 лекция
Недоступна
Упаковка склада
Упаковка склада
1
Опрос
Работа с целыми числами, 2 уровень, 5 лекция
Недоступен
Работа с целыми числами
Работа с целыми числами
Комментарии (4)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Eridan Braus Уровень 4
15 апреля 2026
Второй уровень пройден. Всё еще остаётся надежда, что дальше будут задания посложнее, чем просто сложение двух переменных, потому что весь остальной код фактически уже написан за нас — и не совсем понятно, зачем это было сделано. Из-за этого я даже не могу нормально набить руку на базовых вещах: объявлении переменных, вводе данных и так далее Пока что задания из бесплатного курса на другой платформе кажутся даже сложнее, чем текущие
Мишаня Уровень 7
28 апреля 2026
Согласен. Мне как уже знающему один ЯП, хотелось бы конечно набивать руку на практических заданиях, а не на подстановках нужных значений
Anonymous #133330 Уровень 4
13 апреля 2026
В тесте один вопрос имеет неверный ответ: "Что произойдёт при выполнении fmt.Println(10 / 0) для int? Посколько оба числа указаны явно сразу в выражении, то компилятор сразу видит проблему "деления на ноль" и прерывает сборку программы. А значит, правильным ответом будет "ошибка компиляции". Проверил локально.

$ go build main.go
# command-line-arguments
./main.go:10:19: invalid operation: division by zero
GoRushER_02_04_2027 Уровень 15
7 апреля 2026
2 уровень закрыт. Ееее! Всем успехов!