Ветвление разделяет область входных данных на случаи и выбирает ровно один допустимый путь выполнения. Корректность определяется не числом конструкций if и switch, а тем, полностью ли покрыта область значений, принадлежат ли границы нужным случаям и не скрывает ли ранняя широкая проверка более узкое правило.

Инструкция и выражение решают разные задачи. if и switch управляют действиями, условный оператор и switch-выражение вычисляют значение, а patterns описывают множества входов непосредственно в коде. Перечисление задаёт предметный тип для конечного набора состояний, но его базовый числовой тип допускает значения без именованного члена, поэтому исчерпывающий разбор обязан учитывать не только объявленные имена.

Внешний текст сначала проходит отдельные границы доверия: завершение входного потока, проверку формата, диапазона и предметных ограничений. Parse, TryParse и Convert имеют разные контракты отказа, а числовая строка получает смысл только вместе с культурой и разрешённым стилем. На экспертном уровне система ветвей исследуется как формальный объект: строится таблица решений, доказываются полнота и взаимная исключительность, а диагностика компилятора и исключения времени выполнения рассматриваются как наблюдаемая часть контракта.

Опора — Разделы 9–11: перечисления и модель состояний; ветвления и полнота разбора случаев; консольный ввод-вывод и разбор данных. Дополнительно используются nullable-анализ из Раздела 4, логические выражения и короткое замыкание из Раздела 6, числовые допуски из Раздела 7 и форматирование с явной культурой из Раздела 8.

Каждая задача оформляется отдельной консольной программой полным шаблоном курса и компилируется с <Nullable>enable</Nullable>. Циклы, массивы, собственные методы, рекурсия, исключения, DateTime и конструкции последующих разделов не используются. Консольный ввод обязателен только там, где он прямо указан; в остальных задачах контрольные значения задаются в Main, чтобы проверять именно систему случаев. Получение строки, проверка null, разбор формата, проверка диапазона и предметное вычисление выполняются последовательно: при отказе более поздний этап не запускается. Намеренно некомпилируемые или аварийно завершающиеся варианты исследуются в отдельных копиях проекта без try/catch и без подавления nullable-предупреждений.


База практических заданий

Уровень I. Базовый

Полная система случаев, принадлежность границ, форма ветвления и порядок проверок заданы условием. Требуется точно перенести готовое разбиение в if, условный оператор, switch-инструкцию или switch-выражение и подтвердить каждый путь на указанных данных. Ученик не проектирует дерево решений: сложность растёт от двух готовых случаев к многоступенчатому разбору ввода и таблице состояний.

1. Знак и чётность: Дано целое число value, заданное в Main. Цепочкой if/else if/else вывести один из трёх взаимоисключающих результатов: отрицательное число, ноль или положительное число. Для ненулевого значения отдельным вложенным if определить чётность. В комментарии указать, почему ветвь нуля не пересекается с двумя остальными и почему проверка чётности не изменяет основную классификацию.

2. Проекция на отрезок: Даны вещественные aa, bb и xx, где aba\le b. Условным оператором вычислить проекцию

p={a,x<a,x,axb,b,x>b.p= \begin{cases} a,&x<a,\\ x,&a\le x\le b,\\ b,&x>b. \end{cases}

Использовать вложенную форму x < a ? a : x > b ? b : x. Вывести pp, поправку pxp-x и признак изменения исходного значения. Объяснить, почему условный оператор вычисляет значение, а не является самостоятельной инструкцией управления.

3. Кусочная функция: Дано вещественное число xx. Цепочкой if/else if/else вычислить

f(x)={x2+2x,x<2,4,2x<0,4x2,0x2,x2,x>2.f(x)= \begin{cases} x^2+2x,&x<-2,\\ 4,&-2\le x<0,\\ 4-x^2,&0\le x\le2,\\ x-2,&x>2. \end{cases}

Вывести результат через F6. До запуска указать, каким ветвям принадлежат точки 2-2, 00 и 22, затем подтвердить это контрольными значениями непосредственно по обе стороны каждой границы.

4. Состояние процесса: Объявить

enum ProcessState
{
    Created = 10,
    Validated,
    Running = 20,
    Suspended,
    Completed = 40
}

Для каждого именованного значения выполнить switch-инструкцию, сопоставляющую состоянию отдельное действие; ветвь default оставить для значения без именованного члена. Вывести имя и числовой код состояния. Объяснить автоматические коды Validated и Suspended, размещение enum вне метода и необходимость break в каждой непустой секции.

5. Оценка прописью: Дано целое значение grade. Через switch-выражение получить строку: 2 — «неудовлетворительно», 3 — «удовлетворительно», 4 — «хорошо», 5 — «отлично», любой другой результат — «недопустимая оценка». Использовать constant patterns и _. Сравнить назначение switch-выражения с switch-инструкцией из предыдущей задачи.

6. Категория результата: Дан целый результат испытания. Одним switch-выражением с relational и logical patterns получить категорию:

  • вне [0,100][0,100] — «недопустимо»;
  • 0 — «нулевой результат»;
  • 115959 — «не зачтено»;
  • 60607474 — «зачтено»;
  • 75758989 — «хорошо»;
  • 9090100100 — «отлично».

Использовать arms < 0 or > 100, 0, >= 90, >= 75, >= 60 и _ именно в указанном порядке. Для каждой границы назвать выбранный arm до запуска.

7. Полуинтервал и законы де Моргана: Даны целые a<ba<b и xx. Получить признак принадлежности полуинтервалу [a,b)[a,b):

inside=axx<b.inside=a\le x\land x<b.

Непринадлежность вычислить двумя способами:

outside1=¬inside,outside_1=\neg inside, outside2=x<axb.outside_2=x<a\lor x\ge b.

Вывести все результаты и подтвердить равенство двух форм непринадлежности. Отдельно проверить обе границы и значения непосредственно рядом с ними.

8. Короткое замыкание: Даны целые число измерений nn, их сумма ss и порог tt. Сигнал подаётся, только если n>0n>0 и среднее (double)s/n(double)s/n строго превышает tt. Получить признак одним выражением с &&, расположив вещественное деление справа. Среднее выводить только внутри ветви, в которой уже установлено n>0n>0. Для n=0n=0 объяснить, почему деление не выполняется; в отдельной копии заменить && на & и до запуска предсказать результат.

9. Целое число из входного потока: Вывести приглашение через Console.Write, получить string? через Console.ReadLine и последовательно различить:

  1. входной поток завершён и получено null;
  2. строка не представляет значение Int32;
  3. число не принадлежит диапазону [1000,1000][-1000,1000];
  4. корректное отрицательное число;
  5. ноль;
  6. корректное положительное число.

Использовать int.TryParse; для корректного ненулевого числа дополнительно вывести чётность. Разбор формата не должен смешиваться с предметной классификацией.

10. Машинный коэффициент: Получить одну строку через Console.ReadLine. После отдельной проверки null разобрать положительный decimal с CultureInfo.InvariantCulture и стилем, допускающим ведущий знак, десятичную точку и окружающие пробелы, но запрещающим разделители групп. Различить ошибку формата и неположительное значение. При успехе умножить заданную базовую величину на коэффициент и вывести результат в InvariantCulture через F6. Строка "1,075" не должна приниматься как машинная запись 1.075.

11. Таблица запуска подсистемы: Даны hasPower, hasCooling и interlockClosed. Закодировать их тремя младшими разрядами целого числа: питание — разряд 2, охлаждение — разряд 1, блокировка — разряд 0. По таблице реализовать switch-инструкцию:

КодРезультат
000011питание отсутствует
100отсутствует охлаждение и разомкнута блокировка
101отсутствует охлаждение
110разомкнута блокировка
111запуск разрешён

Коды 03 объединить несколькими case с общим телом. Ветвь default должна сообщать о нарушении внутреннего контракта, хотя корректное построение даёт только значения 07. Вывести три исходных признака, числовой код и результат для всех восьми комбинаций отдельными запусками.


Уровень II. Продвинутый

Условие задаёт предметные требования и допустимые входы, но не готовую систему ветвей. Требуется самостоятельно вывести полный набор случаев, провести границы, выбрать инструкцию или выражение, упорядочить проверки и доказать полноту и взаимную исключительность. В отличие от Уровня I основная работа состоит в проектировании дерева решений; сложность растёт от классификации одного значения к согласованию нескольких признаков, вырожденных моделей и многоступенчатого ввода.

1. Классификатор символа: Дан char. Без char.IsDigit, char.IsLetter и других готовых классификаторов получить через switch-выражение категорию: десятичная цифра, прописная латинская буква, строчная латинская буква, горизонтальный пробельный символ (' ' или '\t'), перевод строки ('\n' или '\r') либо прочий символ. Самостоятельно построить constant, relational и logical patterns и завершить выражение _.

2. Полный разбор линейного уравнения: Даны коэффициенты decimal a и b уравнения ax+b=0ax+b=0. Самостоятельно построить три взаимоисключающих исхода: единственный корень; решений нет; любое число является решением. Корень вычислять только в соответствующей ветви. До реализации составить таблицу решений по признакам a=0a=0 и b=0b=0 и отметить невозможную или несущественную комбинацию для каждой строки.

3. Маржинальная шкала: Дан неотрицательный годовой доход типа decimal. Первые 20_000 облагаются по ставке 10%10\%, часть от 20_000 до 50_000 — по 20%20\%, часть сверх 50_000 — по 30%30\%. Самостоятельно вывести формулу налога для каждого диапазона, вычислить чистый доход и эффективную ставку. Проверить непрерывность функции налога в точках 20_000 и 50_000: переход через порог не должен повторно облагать предыдущую часть дохода.

4. Взаимное положение замкнутых отрезков: Даны [a,b][a,b] и [c,d][c,d], где aba\le b и cdc\le d. Построить взаимоисключающий разбор:

  • отрезки равны;
  • первый строго содержится во втором;
  • второй строго содержится в первом;
  • касаются ровно в одной точке;
  • пересекаются по отрезку положительной длины без строгого вложения;
  • не пересекаются.

Для пересекающихся случаев вывести границы и длину пересечения, для разделённых — величину зазора. Вырожденные отрезки нулевой длины должны обрабатываться теми же правилами без отдельной подмены модели.

5. Точка и концентрическое кольцо: Даны (x,y)(x,y), радиусы 0<r<R0<r<R и допуск ε>0\varepsilon>0, измеряемый в единицах квадрата расстояния. До классификации исключить NaN и бесконечности. По d2=x2+y2d^2=x^2+y^2 определить: внутри малой окружности; на её границе; в открытом кольце; на границе большой окружности; вне большой окружности. Проверки границ должны предшествовать открытым областям. Обосновать отсутствие разрывов и пересечений в полученной системе случаев.

6. Калькулятор операций: Даны два double и символ +, -, *, / или ^. Самостоятельно выбрать switch-инструкцию либо switch-выражение. Деление на значение, близкое к нулю по заданному допуску, является отдельным отказом; неизвестная операция — другим. Для степени использовать Math.Pow. Вычислительный результат не должен использоваться после ветви отказа, а NaN или бесконечность успешной операции должны быть отмечены отдельно.

7. Политика контроля качества: Объявить InspectionMode со значениями Normal, Precision, Emergency. Даны режим, абсолютная ошибка и аварийный сигнал. Через switch-выражение с when получить решение:

  • Emergency с сигналом — немедленная остановка;
  • Emergency без сигнала — удержание результата;
  • Precision при ошибке не более 0.01 — принятие;
  • Precision при ошибке не более 0.05 — ручная проверка;
  • Normal при ошибке не более 0.10 — принятие;
  • Normal при ошибке не более 0.50 — ручная проверка;
  • остальные именованные случаи — отклонение;
  • значение без именованного члена — нарушение контракта.

Самостоятельно определить порядок arms, чтобы широкая граница не скрыла узкую, и не сливать неизвестное значение enum с обычным отклонением.

8. Смешанный протокол ввода: Программа читает три строки: денежную сумму в пользовательском формате ru-RU, машинный коэффициент в InvariantCulture и целое количество. Для каждого поля отдельно различить null, ошибочный формат и нарушение диапазона: сумма неотрицательна, коэффициент положителен, количество положительно. Только после трёх успешных этапов вычислить итог. Строки "1250,50" и "1.075" должны интерпретироваться разными культурами; сообщение должно точно указывать первое поле, на котором остановилась обработка.

9. Полная классификация треугольника: Даны три double, предполагаемые длины сторон, и положительный допуск. Последовательно отделить NaN и бесконечности, неположительную длину, вырожденность и невозможность существования. Для существующего треугольника определить тип по сторонам — равносторонний, равнобедренный или разносторонний — и по наибольшему углу — остроугольный, прямоугольный или тупоугольный. Массивы и сортировка запрещены: наибольшую сторону получить через Math.Max. В комментарии зафиксировать, в каких единицах применяется допуск при сравнении длин и квадратов.

10. Квадратное уравнение с вырождением: Даны double a, b, c и ZeroTolerance. Если aa близко к нулю, свести задачу к линейному уравнению и разобрать его три исхода. Иначе вычислить дискриминант и различить два вещественных корня, один корень и отсутствие вещественных корней с учётом допуска около нуля. Для двух корней вывести их в неубывающем порядке и подстановочные невязки. До реализации перечислить все листья дерева и доказать, что для допустимого набора коэффициентов достигается ровно один лист.

11. Проверка даты и следующий день: Программа читает день, месяц и год тремя строками. Для каждой строки отдельно различить null, ошибочный формат и выход из базового диапазона; год принадлежит [1,9999][1,9999], месяц — [1,12][1,12]. После успешного разбора определить високосность, длину месяца через switch-выражение и допустимость дня. Для корректной даты вычислить следующий день без DateTime: обычное увеличение дня, переход к следующему месяцу и переход с 31 декабря на 1 января следующего года являются разными ветвями. Дата 31.12.9999 корректна, но не имеет представимого преемника в принятой модели. Доказать полноту классификации месяцев и взаимную исключительность итоговых исходов.


Уровень III. Экспертный

Задача задаёт вопрос о границах языка, контракта разбора или формальной системы решений, а не только требуемый результат. До эксперимента требуется предсказать диагностику, исключение, выбранную ветвь или нарушение свойства; после — сопоставить наблюдение с правилами enum, pattern matching, nullable-анализа, культуры и порядка проверок. Обязательная часть решения — таблица или доказательство полноты, достижимости и взаимной исключительности.

1. Значение enum без имени и исчерпывающий switch: Объявить

enum ProtocolState : byte
{
    Idle = 1,
    Ready = 2,
    Running = 4,
    Failed = 8
}

Явным приведением получить значения из кодов 1, 2, 4, 8, 3 и 255. Для каждого вывести числовой код и строковое представление. Затем сравнить:

  1. switch-инструкцию с default;
  2. switch-выражение только с четырьмя именованными arms;
  3. исправленное switch-выражение с _.

До сборки предсказать предупреждение второй версии; в отдельном запуске передать код 3 и зафиксировать поведение. Объяснить, почему явное преобразование не проверяет наличие именованного члена и чем полнота по именам отличается от полноты по всем значениям базового типа.

2. Порядок arms, пересечение и недостижимость: Исследовать несколько switch-выражений над int score:

  • корректное: < 0 or > 100, >= 90, >= 75, >= 60, _;
  • с arm >= 60, расположенным раньше >= 90;
  • с _, расположенным не последним;
  • с двумя arms одного constant pattern;
  • с guards >= 60 when isPriority и >= 60.

Для каждого варианта до сборки определить, будет ли он принят компилятором, какие arms недостижимы и где порядок меняет результат, хотя компиляция допустима. Затем построить таблицу множеств значений для корректной версии и доказать, что после учёта порядка каждый допустимый int выбирает ровно один arm.

3. Контракты Parse, TryParse и Convert: Для строк "42", " 42 ", "", "12.5", "2147483648" и "abc" сравнить способы получения int. Для TryParse записать логический результат и выходное значение; каждый потенциально аварийный вызов Parse и Convert.ToInt32(string) выполнять в отдельной копии проекта без обработки исключения. Для null исследовать int.TryParse(null, out value), Convert.ToInt32((string?)null) и вызов int.Parse без подавления nullable-предупреждения. Составить таблицу: успех, формат, переполнение, отсутствие значения, диагностика компилятора и результат выходного параметра. Объяснить, почему ожидаемая ошибка пользовательского ввода не должна моделироваться исключением.

4. Одна строка в разных культурах: Используя ru-RU, en-US и InvariantCulture, исследовать строки "1,234", "1.234", "1 234,5" и "1234.5". Для каждой пары «строка — культура» применить decimal.TryParse с NumberStyles.Number, записать успешность и принятое значение, затем отформатировать его обратно той же культурой. Отдельно построить строгий машинный стиль, разрешающий знак, десятичную точку и окружающие пробелы, но запрещающий группировку. До запуска предсказать неоднозначные случаи. Объяснить, почему замена запятой точкой не является корректным общим алгоритмом и почему формат обмена обязан фиксировать культуру и NumberStyles.

5. Доказательство полноты политики доступа: Объявить Role со значениями Guest, Researcher, Administrator. Даны роль, isAuthenticated, emergencyMode и safetyPassed. Политика:

  1. неаутентифицированный пользователь всегда получает отказ;
  2. в аварийном режиме администратор получает полный аварийный доступ, исследователь — только чтение, гость — отказ;
  3. в обычном режиме пользователь без проверки безопасности получает отказ;
  4. в обычном безопасном режиме гость получает наблюдательный доступ, исследователь — рабочий, администратор — полный.

До кода построить таблицу решений с условиями «неважно», затем преобразовать её в упорядоченное switch-выражение по Role с when. Доказать полноту и взаимную исключительность после учёта порядка. В отдельной версии поставить широкое правило раньше узкого и показать недостижимость либо неверный результат. Значение роли без имени должно обрабатываться как нарушение контракта, а не как Guest.

6. Устойчивость числовой границы: Требуется классифицировать double x относительно порога tt как «ниже», «на границе» или «выше». Сравнить три политики:

  1. точное равенство x == t;
  2. абсолютный допуск xtε|x-t|\le\varepsilon;
  3. комбинированный допуск
xtabsTol+relTolmax(x,t).|x-t| \le absTol+relTol\max(|x|,|t|).

Для каждой политики сначала исключить NaN и бесконечности, затем построить полную систему ветвей без промежутков и пересечений. Исследовать пары 0.1 + 0.2 и 0.3, а также 101210^{12} и 1012+110^{12}+1. До запуска предсказать классификацию при нескольких допусках; после объяснить, почему порядок «нижняя область — граница — верхняя область» может давать иной результат, чем проверка границы первой, если условия сформулированы с пересечением. Итогом должна стать политика с явно заданными единицами и масштабом допуска.


Обязательные контрольные наборы

ЗадачаИсходные данные
I.1-8, -7, 0, 12, 15
I.2(a,b,x)=(2,5,7)(a,b,x)=(-2,5,-7), (2,5,3)(-2,5,3), (2,5,9)(-2,5,9)
I.3x{3,2,0,1,0,2,2,1}x\in\{-3,-2,-0{,}1,0,2,2{,}1\}
I.4все именованные значения; отдельно (ProcessState)12
I.52, 3, 4, 5, 6
I.61-1, 00, 5959, 6060, 7474, 7575, 8989, 9090, 100100, 101101
I.7(a,b,x)=(3,5,4)(a,b,x)=(-3,5,-4), (3,5,3)(-3,5,-3), (3,5,4)(-3,5,4), (3,5,5)(-3,5,5), (3,5,8)(-3,5,8)
I.8(n,s,t)=(0,100,10)(n,s,t)=(0,100,10), (4,50,10)(4,50,10), (4,40,10)(4,40,10)
I.9"-17", "0", "23", "12.5", "2147483648", null
I.10"1.075", " -0.25 ", "1,075", "", null
I.11все восемь комбинаций трёх логических признаков
II.1'7', 'A', 'z', ' ', '\t', '\n', 'Ж', '+'
II.2(a,b)=(2,6)(a,b)=(2,-6), (0,5)(0,5), (0,0)(0,0)
II.30, 20_000, 20_000.01, 50_000, 50_000.01, 80_000
II.4[1,1][1,1] и [1,1][1,1]; [0,10][0,10] и [2,8][2,8]; [0,5][0,5] и [5,9][5,9]; [0,7][0,7] и [4,10][4,10]; [0,3][0,3] и [8,12][8,12]
II.5(0,0)(0,0), (3,4)(3,4), (5,0)(5,0), (6,0)(6,0), (7,0)(7,0), (8,0)(8,0) при r=5r=5, R=7R=7, ε=109\varepsilon=10^{-9}
II.6(2,3,+)(2,3,'+'), (2,3,^)(2,3,'\char94'), (5,0,/)(5,0,'/'), (5,1014,/)(5,10^{-14},'/'), (2,3,?)(2,3,'?')
II.7все три режима; ошибки 0.01, 0.05, 0.10, 0.50; (InspectionMode)99
II.8"1250,50", "1.075", "3"; затем по одному отсутствующему, неверному и выходящему за диапазон полю
II.9(3,3,3)(3,3,3), (3,4,5)(3,4,5), (2,3,4)(2,3,4), (1,2,3)(1,2,3), (1,2,2)(-1,2,2), NaN
II.10(0,0,0)(0,0,0), (0,0,5)(0,0,5), (0,2,6)(0,2,-6), (1,3,2)(1,-3,2), (1,2,1)(1,2,1), (1,0,1)(1,0,1) и случай DZeroTolerance\lvert D\rvert\le ZeroTolerance
II.1128.02.2024, 28.02.2023, 29.02.2024, 31.04.2024, 31.12.9998, 31.12.9999; ошибочные и отсутствующие строки
III.1коды 1, 2, 4, 8, 3, 255
III.2score =1,0,59,60,74,75,89,90,100,101=-1,0,59,60,74,75,89,90,100,101; оба значения isPriority
III.3строки и null из условия
III.4все четыре строки во всех трёх культурах и в строгом машинном стиле
III.5все комбинации трёх логических признаков для каждой роли; (Role)99
III.6пары из условия; absTol{0,1012,0,5}absTol\in\{0,10^{-12},0{,}5\}, relTol{0,1012}relTol\in\{0,10^{-12}\}; NaN, PositiveInfinity

Итог модуля

Ученик преобразует область входных данных в полную и взаимоисключающую систему случаев, выбирает между if, условным оператором, switch-инструкцией и switch-выражением и управляет порядком constant, relational и logical patterns, _ и when. Перечисление используется как предметный тип без ложного предположения, что любое значение его базового типа имеет имя. Внешний ввод проходит последовательные границы null → формат → диапазон → предметное вычисление, а выбор Parse, TryParse, Convert, культуры и NumberStyles следует из явного контракта. Корректность подтверждается таблицей решений, граничными проверками и объяснением достижимости, полноты и поведения отказа.

Покрытие опоры и границы

  • Объявление, нумерация, типобезопасность и неименованные значения enum: I.4, II.7, III.1, III.5.
  • if, else if, else, вложенные ветви и принадлежность границ: I.1, I.3, I.8–I.10, II.2–II.6, II.8–II.11.
  • Условный оператор: I.2; различие выражения и инструкции закрепляется сопоставлением I.2, I.4 и I.5.
  • switch-инструкция, constant patterns и объединённые секции: I.4, I.11.
  • switch-выражение, relational и logical patterns, _, when, порядок и исчерпываемость: I.5–I.6, II.1, II.7, II.11, III.1–III.2, III.5.
  • Полнота, взаимная исключительность, достижимость и таблицы решений: I.3, I.6–I.7, II.2–II.11, III.2, III.5–III.6.
  • Логические выражения, законы де Моргана и короткое замыкание: I.7–I.8, III.5.
  • Console.Write, Console.WriteLine, Console.ReadLine, string? и завершение входного потока: I.9–I.10, II.8, II.11.
  • Parse, TryParse, Convert, диапазон и разделение этапов обработки: I.9–I.10, II.8, II.11, III.3.
  • Пользовательская и машинная культура, InvariantCulture и NumberStyles: I.10, II.8, III.4.
  • Числовые допуски, NaN и бесконечности как часть корректной системы ветвей: II.5–II.6, II.9–II.10, III.6.

За границей: повторный запрос до получения допустимого значения относится к Модулю 3 после введения циклов. Собственные методы, типовые, свойственные и позиционные patterns, обработка исключений, массивы и DateTime не используются. Разбор сложных текстовых форматов, регулярные выражения и объектная валидация относятся к последующим модулям и Курсу II.