Перейти к содержимому

Практика 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. Разминка

  1. Создайте перечисление Direction с вариантами north, south, east, west. Добавьте:
    • Raw values типа String (например, "N", "S", "E", "W").
    • Вычисляемое свойство opposite, возвращающее противоположное направление.
    • Инициализацию из raw value и обработку nil.

Пример ожидаемого поведения:

let dir = Direction.north
print(dir.opposite) // south
print(dir.rawValue) // "N"
if let d = Direction(rawValue: "E") {
print(d) // east
}
  1. Создайте перечисление HTTPStatus с raw values типа Int:
    • ok = 200, notFound = 404, serverError = 500.
    • Добавьте вычисляемое свойство description: String, возвращающее человекочитаемое описание.
    • Используйте switch по переменной типа HTTPStatus и выведите описание каждого статуса.

B. Ассоциированные значения

  1. Создайте перечисление 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
  1. Создайте enum MathOperation с case add(Double, Double), subtract(Double, Double), multiply(Double, Double), divide(Double, Double). Создайте собственный enum Result с case success(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.0
case .failure(let msg): print("Ошибка: \(msg)")
}
switch calculate(op2) {
case .success(let value): print("Результат: \(value)")
case .failure(let msg): print("Ошибка: \(msg)") // Ошибка: Деление на ноль
}

C. Рекурсивные перечисления

  1. Создайте 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)) // 930
printTree(project, indent: "")
// project/
// main.swift (200)
// Sources/
// app.swift (500)
// utils.swift (150)
// README.md (80)

D. Pattern Matching

  1. Дан массив значений 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 мм
  1. Реализуйте функцию 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. Конечные автоматы

  1. Создайте конечный автомат для состояния пользовательской сессии:
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") // Вход: alice
session.ban(reason: "спам") // Бан: спам
session.login(username: "bob") // Ошибка: нельзя войти в состоянии banned
session.appeal() // Апелляция принята
session.login(username: "alice") // Вход: alice

F. Мини-проект

  1. «Калькулятор выражений» — создайте рекурсивный 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) * 2
let expr: Expression = .multiplication(
.addition(.number(3), .number(5)),
.number(2)
)
print(evaluate(expr)) // 16.0
print(describe(expr)) // "(3.0 + 5.0) * 2.0"
  1. Объедините предыдущие задания: создайте 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-варианта).