Программист решает не «задачу», а задачу в контексте
В учебниках формулировки задач даны в вакууме. В реальной работе любая задача приходит с контекстом: где будет использоваться программа, кто её заказчик, какие данные на входе, что считается ошибкой. Разберём три типичных примера.
Формулировка обманчиво проста
«Решить линейное уравнение» — понятно каждому школьнику. Но что если это уравнение с комплексными числами или с матрицами? Постановка меняется — меняется и код.
Реальный объект ≠ примитивный тип
Адрес — это не «одна строка». Время года на Марсе отличается от земного. Программа, которая этого не учитывает, формально работает, а по сути — нет.
Правильный ответ зависит от контекста
Уравнение 0 · x = b имеет бесконечно много решений при b = 0
и ни одного при b ≠ 0. Наивная программа скажет «x = b / 0» и упадёт.
Пример 1: «решить линейное уравнение»
Одна формулировка — пять совершенно разных задач. Выберите контекст и посмотрите, как меняется постановка и код.
Пример 2: «адрес» — это не строка
Кажется очевидным: адрес — это текст. Но в реальных задачах адрес состоит из многих частей и используется по-разному.
❌ Наивно: адрес — одна строка
struct Client {
string name;
string address; // ← "г. Москва, ул. Пушкина, д. 10, кв. 5"
string phone;
};
// Проблемы:
// 1. Нельзя вывести всех клиентов из
// конкретного города.
// 2. Нельзя проверить корректность
// индекса или номера дома.
// 3. Сортировка по городу/улице
// невозможна без парсинга строки.
// 4. Дубли домов и корпусов
// приходится обрабатывать
// вручную.
// 5. Нет проверки, что
// адрес вообще существует.
✓ Продуманно: адрес — составной объект
struct Address {
string country;
string region;
string city;
string street;
int house;
string building; // корпус, литера
int apartment;
string postal_code;
};
struct Client {
string name;
Address address;
string phone;
};
// Что это даёт:
// 1. Можно отфильтровать по городу:
// if (c.address.city == "Москва")
// 2. Проверить корректность
// индекса и номера дома.
// 3. Сортировка по любому полю.
// 4. Легко добавить новые поля
// (например, «район»).
Почему это важно на практике
В банковском приложении, в доставке, в госуслугах адрес используется каждый день: «найти всех клиентов в этом городе», «проверить, что индекс соответствует региону», «отсортировать по улице». Если адрес — одна строка, всё это превращается в хрупкий парсинг с регулярками — источник багов.
Проектирование структуры данных — это первый шаг решения задачи, и часто он важнее, чем сам алгоритм.
struct Address, struct FullName.
Преподаватель обычно это приветствует.
Пример 3: «вывести время года по номеру месяца»
Классическая задача для отработки switch. Но если планета —
не Земля, всё меняется. А на Земле ответ зависит от полушария.
❌ Наивно: только для Земли и северного полушария
switch (month) {
case 12: case 1: case 2:
cout << "Зима"; break;
case 3: case 4: case 5:
cout << "Весна"; break;
case 6: case 7: case 8:
cout << "Лето"; break;
case 9: case 10: case 11:
cout << "Осень"; break;
}
// Работает ровно для одного случая:
// Земля, Северное полушарие.
// В Австралии — неверно.
// На Марсе — неверно.
// На Венере — бессмысленно.
✓ Продуманно: с учётом полушария и планеты
// Сезон = функция от планеты и полушария
Season getSeason(Planet p, Hemisphere h, int month) {
if (p == VENUS) return NO_SEASONS;
// Для простоты — сдвиг на полгода
// для южного полушария
int m = (h == SOUTH) ? ((month + 5) % 12) + 1 : month;
// Длины времён года зависят от планеты:
// на Марсе наклон оси и орбита другие.
return planetSeasonTable[p][m];
}
// Что даёт:
// • Корректный результат на Земле везде.
// • Легко добавить Марс, Титан и другие.
// • Легко объяснить заказчику.
Что скрывается за простой формулировкой
Полушарие. На Земле южное полушарие имеет противоположные сезоны: январь — лето, июль — зима.
Широта. На экваторе нет четырёх сезонов — есть сезон дождей и сухой сезон. В полярных областях — полярный день и полярная ночь.
Планета. Времена года определяются наклоном оси вращения и эксцентриситетом орбиты. У Венеры наклон оси всего 3° — времён года нет. У Урана ось наклонена почти на 98° — там экстремальные сезоны по 42 года.
Учебный смысл. Даже учебная задача на switch
может быть поставлена так, что нужно задуматься о контексте — а не просто
«переписать формулу из учебника».
Что ещё прячется за простыми формулировками
Ещё несколько примеров, где наивный подход ломается о реальность.
«Найти среднее значение»
Среднее арифметическое? Медиана? Взвешенное? Среднее по времени (с учётом timestamp)? Для финансовых данных — скользящее среднее. Для отчёта по ошибкам — медиана устойчивее к выбросам. В каждой области своё «среднее».
«Найти расстояние между точками»
На плоскости — теорема Пифагора. На сфере (Земля) — формула гаверсинусов. В метрике такси — сумма модулей разностей координат. В пространстве Минковского — с другим знаком у времени. Выбор метрики определяется задачей.
«Проверить, является ли число простым»
Для учебной задачи — перебор делителей до √n. Для криптографии с числами в 2048 бит — вероятностные тесты Миллера-Рабина. Для 1 — не простое по определению. Для отрицательных — не определено.
«Отсортировать список студентов»
По алфавиту фамилии? С учётом «ё» и регистра? По среднему баллу? По дате зачисления? Что если две фамилии совпадают — какой второй критерий? Стабильная сортировка или нет?
«Найти максимальный элемент»
Если массив пуст? Если несколько максимумов — вернуть первый, последний или все? Если элементы — числа с плавающей точкой, то как сравнивать NaN? Есть ли отрицательные значения?
«Прочитать число с клавиатуры»
Целое или вещественное? Может ли быть отрицательным? Что делать, если пользователь ввёл букву? Разделитель — точка или запятая? Максимальное значение?
Чек-лист перед началом работы
Задайте эти вопросы — сначала себе, потом (если есть возможность) заказчику. Ответы часто меняют структуру программы.
0.1 + 0.2 ≠ 0.3. Для денег нужны целые копейки или decimal.Пример: как продуманная структура упрощает код
Сравним две реализации задачи «найти всех клиентов из заданного города».
// Простая структура
struct Client {
string name;
string address; // "г. Москва, ул. Пушкина, д. 10"
};
// Поиск клиентов по городу
vector<Client> fromCity(
const vector<Client>& clients,
const string& city
) {
vector<Client> result;
for (const auto& c : clients) {
// Ищем подстроку «г. Москва» — но что если
// пользователь ввёл без «г.» или с «город Москва»?
if (c.address.find(city) != string::npos) {
result.push_back(c);
}
}
return result;
}
// Сломалось, если:
// • другой формат записи;
// • город — часть названия улицы;
// • опечатка в исходных данных.
struct Address {
string country;
string city;
string street;
int house;
};
struct Client {
string name;
Address address;
};
// Поиск клиентов по городу
vector<Client> fromCity(
const vector<Client>& clients,
const string& city
) {
vector<Client> result;
for (const auto& c : clients) {
if (c.address.city == city) { // точно и надёжно
result.push_back(c);
}
}
return result;
}
// Работает независимо от порядка слов,
// регистра, формата ввода.
// Можно легко добавить поиск по региону,
// по улице, по индексу.
Задание
- Возьмите одну задачу из курсовых или лабораторных и выпишите ответы на все вопросы чек-листа. Обсудите с преподавателем.
- Для задачи «найти среднее значение чисел» определите, что именно нужно: среднее арифметическое, медиана, взвешенное или что-то ещё. Обоснуйте выбор.
- Спроектируйте модель данных для «ФИО» так, чтобы учитывать ситуации с отсутствующим отчеством, двойной фамилией, фамилией с дефисом.
- Расширьте пример с временами года: добавьте поддержку Луны (для неё сезонов нет — только освещённость) и Титана (спутник Сатурна, с methane seas, но с seasons).
- Найдите в интернете два разных формата адресов (например, российский и американский). Какие поля нужны для каждого? Спроектируйте общую модель, которая поддерживает оба.
- Придумайте формулировку задачи, где слово «среднее» означает три разных вещи в зависимости от контекста, и покажите на примерах, как меняется ответ.