Практика 9. Практика к лекции 9
Практика 9. Перечисления и Pattern Matching
Цель: закрепить работу с перечислениями Swift (raw values, associated values, методы, рекурсивные enum) и шаблонным сопоставлением (switch, if case, for case, where) через серию упражнений от базовых к проектным. Все задания выполняются на Linux в командной строке.
Рекомендации по выполнению:
- Создавайте файлы с расширением
.swiftи запускайте командойswift file.swift. - Каждое задание можно оформлять в отдельном файле или объединять логически близкие в один.
- Добавляйте
print-вызовы для демонстрации работы каждого задания. - Сравнивайте подход Swift с аналогами в Python (
Enum,match/caseиз Python 3.10+).
A. Разминка
- Создайте перечисление
Directionс вариантамиnorth,south,east,west. Добавьте:- Raw values типа
String(например,"N","S","E","W"). - Вычисляемое свойство
opposite, возвращающее противоположное направление. - Инициализацию из raw value и обработку
nil.
- Raw values типа
Пример ожидаемого поведения:
let dir = Direction.northprint(dir.opposite) // southprint(dir.rawValue) // "N"
if let d = Direction(rawValue: "E") { print(d) // east}- Создайте перечисление
HTTPStatusс raw values типаInt:ok = 200,notFound = 404,serverError = 500.- Добавьте вычисляемое свойство
description: String, возвращающее человекочитаемое описание. - Используйте
switchпо переменной типаHTTPStatusи выведите описание каждого статуса.
B. Ассоциированные значения
- Создайте перечисление
Coinс вариантамиkopeck(Int),ruble(Int),dollar(Int)(ассоциированное значение — номинал). Напишите функциюtotalInRubles(_ coins: [Coin]) -> Double, которая считает общую сумму в рублях (курс: 1 доллар = 90.0 рублей, 100 копеек = 1 рубль).
Пример ожидаемого поведения:
let wallet: [Coin] = [.ruble(50), .dollar(1), .kopeck(75), .ruble(10)]print(totalInRubles(wallet)) // 150.75- Создайте
enum MathOperationс caseadd(Double, Double),subtract(Double, Double),multiply(Double, Double),divide(Double, Double). Создайте собственныйenum Resultс casesuccess(Double)иfailure(String). Напишите функциюcalculate(_ op: MathOperation) -> Result, возвращающую результат вычисления или ошибку при делении на ноль.
Пример ожидаемого поведения:
let op1 = MathOperation.add(3.0, 5.0)let op2 = MathOperation.divide(10.0, 0.0)
switch calculate(op1) {case .success(let value): print("Результат: \(value)") // Результат: 8.0case .failure(let msg): print("Ошибка: \(msg)")}
switch calculate(op2) {case .success(let value): print("Результат: \(value)")case .failure(let msg): print("Ошибка: \(msg)") // Ошибка: Деление на ноль}C. Рекурсивные перечисления
- Создайте
indirect enum FileSystemItemс вариантамиfile(name: String, size: Int)иfolder(name: String, contents: [FileSystemItem]). Напишите:- Функцию
totalSize(_ item: FileSystemItem) -> Int, рекурсивно вычисляющую суммарный размер. - Функцию
printTree(_ item: FileSystemItem, indent: String), выводящую дерево файловой системы с отступами.
- Функцию
Пример ожидаемого поведения:
let project: FileSystemItem = .folder(name: "project", contents: [ .file(name: "main.swift", size: 200), .folder(name: "Sources", contents: [ .file(name: "app.swift", size: 500), .file(name: "utils.swift", size: 150) ]), .file(name: "README.md", size: 80)])
print(totalSize(project)) // 930printTree(project, indent: "")// project/// main.swift (200)// Sources/// app.swift (500)// utils.swift (150)// README.md (80)D. Pattern Matching
- Дан массив значений
enum Weather:
enum Weather { case sunny(tempC: Double) case rainy(tempC: Double, mmPrecipitation: Double) case snowy(tempC: Double, cmSnow: Double) case cloudy}Создайте массив из 7 элементов (по одному на каждый день недели). С помощью for case let ... where:
- Выведите только дождливые дни с осадками более 10 мм.
- Выведите только дни с температурой ниже 0°C (любой тип погоды с температурой).
С помощью if case let:
- Проверьте, является ли первый день солнечным, и если да — выведите температуру.
Пример ожидаемого поведения:
let week: [Weather] = [ .sunny(tempC: 25.0), .rainy(tempC: 15.0, mmPrecipitation: 12.5), .cloudy, .snowy(tempC: -5.0, cmSnow: 20.0), .rainy(tempC: 10.0, mmPrecipitation: 3.0), .sunny(tempC: 28.0), .rainy(tempC: 8.0, mmPrecipitation: 15.0)]
// Дождь > 10 мм:// rainy: 15.0°C, 12.5 мм// rainy: 8.0°C, 15.0 мм- Реализуйте функцию
describe(_ value: Any) -> String, которая использует конструкциюswitch valueс паттернами:is Int— «Целое число»let s as String where s.isEmpty— «Пустая строка»let s as String— «Строка: (s)»let arr as [Int] where arr.count > 3— «Большой массив Int ((arr.count) элементов)»default— «Неизвестный тип»
E. Конечные автоматы
- Создайте конечный автомат для состояния пользовательской сессии:
enum SessionState { case guest case loggedIn(username: String) case banned(reason: String)}Реализуйте struct UserSession с:
- Свойством
state: SessionState(начальное —.guest). - Метод
login(username: String)— допустим только изguest. - Метод
ban(reason: String)— допустим только изloggedIn. - Метод
logout()— допустим только изloggedIn. - Метод
appeal()— допустим только изbanned, переводит вguest. - Каждый метод должен печатать ошибку при недопустимом переходе (используйте
guard case).
Пример ожидаемого поведения:
var session = UserSession()session.login(username: "alice") // Вход: alicesession.ban(reason: "спам") // Бан: спамsession.login(username: "bob") // Ошибка: нельзя войти в состоянии bannedsession.appeal() // Апелляция принятаsession.login(username: "alice") // Вход: aliceF. Мини-проект
- «Калькулятор выражений» — создайте рекурсивный enum для представления арифметических выражений:
indirect enum Expression { case number(Double) case addition(Expression, Expression) case subtraction(Expression, Expression) case multiplication(Expression, Expression) case division(Expression, Expression)}Реализуйте:
- Функцию
evaluate(_ expr: Expression) -> Doubleс рекурсивным вычислением. - Функцию
describe(_ expr: Expression) -> String, возвращающую строковое представление выражения в инфиксной нотации со скобками. - Обработку деления на ноль (возвращайте
Double.infinity).
Пример ожидаемого поведения:
// (3 + 5) * 2let expr: Expression = .multiplication( .addition(.number(3), .number(5)), .number(2))print(evaluate(expr)) // 16.0print(describe(expr)) // "(3.0 + 5.0) * 2.0"- Объедините предыдущие задания: создайте
enum Command, моделирующий команды интерактивного калькулятора:.calculate(MathOperation),.history,.clear,.quit. Реализуйте цикл, который читает команды из предопределённого массива и выполняет их, сохраняя историю результатов.
G. Критерии оценивания
- Корректность и полнота реализации: 0–5 баллов
- Правильное использование enum, associated/raw values, indirect: 0–4 балла
- Грамотное применение pattern matching (
switch,if case,for case,where): 0–4 балла - Читаемость кода и наличие демонстрационных примеров: 0–3 балла
Максимум: 16 баллов. Бонус до +2 за дополнительные задания.
H. Дополнительно (по желанию)
- Добавьте в
FileSystemItemметодfind(name: String) -> [FileSystemItem], рекурсивно ищущий элементы по имени. - Реализуйте enum
JSON(null,bool(Bool),int(Int),double(Double),string(String),array([JSON]),object([String: JSON])) и функциюprettyPrint. - Сравните производительность
switchпо enum с ассоциированными значениями и цепочкиif-elseпо классам (аналог Python-варианта).