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

Практика 6. Практика к лекции 6

Цель: закрепить безопасную работу с отсутствием значений в Swift — опциональные типы (Optional), принудительное извлечение, if let, guard let, optional chaining, оператор ??, compactMap — через серию упражнений от базовых к проектным.

Рекомендации по выполнению:

  • Выполняйте задания в отдельных файлах .swift (например, task1.swift, task2.swift).
  • Запускайте через терминал: swift task1.swift (или swift имя_файла.swift).
  • Для проектов из нескольких файлов используйте Swift Package Manager (swift package init --type executable).
  • Работа ведётся на Linux — Xcode не требуется, достаточно установленного Swift toolchain.
  • Избегайте ! (force unwrapping) во всех заданиях, кроме случаев, где это явно указано.

A. Разминка

  1. Напишите функцию safeDivide(_ a: Double, _ b: Double) -> Double?, возвращающую результат деления или nil при делении на ноль.
print(safeDivide(10, 3) as Any) // Optional(3.3333333333333335)
print(safeDivide(10, 0) as Any) // nil

Дополнительно: используйте if let для красивого вывода результата — "10 / 3 = 3.33" или "Деление на ноль".

  1. Напишите функцию firstPositive(_ numbers: [Int]) -> Int?, возвращающую первое положительное число или nil, если таких нет.
print(firstPositive([-3, -1, 0, 4, 7]) as Any) // Optional(4)
print(firstPositive([-3, -1, 0]) as Any) // nil

Подсказка: используйте метод first(where:).


B. if let и guard let

  1. Дан словарь [String: String] с данными пользователя. Напишите функцию greetUser(_ data: [String: String]), которая с помощью guard let извлекает ключи "name" и "age" (преобразуя значение возраста в Int), и выводит приветствие или сообщение об ошибке.
greetUser(["name": "Олег", "age": "22"]) // Привет, Олег! Тебе 22 лет.
greetUser(["name": "Анна"]) // Ошибка: неполные данные
greetUser(["name": "Иван", "age": "abc"]) // Ошибка: неполные данные
  1. Напишите функцию parseConfig(_ raw: [String: String]) -> (host: String, port: Int, path: String), которая извлекает из словаря ключи "host", "port" (преобразуя в Int) и "path". Если хотя бы одного ключа нет или port не является числом, верните значения по умолчанию: ("localhost", 8080, "/"). Используйте ?? (nil-coalescing).
let config1 = ["host": "example.com", "port": "3000", "path": "/api"]
let config2 = ["host": "myserver.ru"]
print(parseConfig(config1)) // ("example.com", 3000, "/api")
print(parseConfig(config2)) // ("myserver.ru", 8080, "/")

C. Optional chaining

  1. Создайте три структуры с опциональными вложенными свойствами:
struct Employee {
let name: String
let position: String?
}
struct Department {
let title: String
var head: Employee?
}
struct Company {
let name: String
var department: Department?
}

Создайте несколько экземпляров Company — с полностью заполненными данными и с nil на разных уровнях. Используя optional chaining и ??, безопасно выведите должность руководителя отдела:

let company1 = Company(name: "TechCorp",
department: Department(title: "iOS",
head: Employee(name: "Алиса", position: "Lead")))
let company2 = Company(name: "StartupX", department: nil)
print(company1.department?.head?.position ?? "Неизвестно") // Lead
print(company2.department?.head?.position ?? "Неизвестно") // Неизвестно
  1. Дан массив опциональных словарей [ [String: String]? ]. С помощью optional chaining извлеките значение по ключу "email" из каждого словаря. Соберите все найденные email-адреса в массив (без nil). Используйте compactMap.
let records: [[String: String]?] = [
["name": "Анна", "email": "anna@mail.ru"],
nil,
["name": "Борис"],
["name": "Виктор", "email": "victor@gmail.com"]
]
// Ожидаемый результат: ["anna@mail.ru", "victor@gmail.com"]

D. compactMap и преобразования

  1. С помощью compactMap преобразуйте массив строк в [Double], отбросив некорректные значения. Затем найдите среднее арифметическое с помощью reduce.
12.2125
let data = ["3.14", "abc", "2.71", "", "1.0", "not_a_number", "42"]
// Валидные: [3.14, 2.71, 1.0, 42.0]
  1. Напишите функцию safeIntArray(_ strings: [String]) -> [Int], которая принимает массив строк и возвращает массив целых чисел, безопасно преобразуя каждую строку (используя compactMap и Int.init). Затем используйте guard для проверки, что массив не пуст, и выведите минимальное и максимальное значения.
safeIntArray(["10", "abc", "20", "", "5"])
// Результат: [10, 20, 5]
// Мин: 5, Макс: 20
safeIntArray(["abc", "xyz"])
// Результат: [] — нет корректных чисел

E. Практические паттерны

  1. Напишите функцию findUser(byId id: Int, in database: [[String: Any]]) -> String, которая ищет пользователя по "id" в массиве словарей. Функция должна:
    • Использовать first(where:) для поиска.
    • Использовать guard let для извлечения "name" из найденного словаря.
    • Возвращать "Пользователь: <имя>" или "Не найден".
let users: [[String: Any]] = [
["id": 1, "name": "Алиса", "age": 25],
["id": 2, "name": "Борис"],
["id": 3, "age": 30] // нет имени
]
print(findUser(byId: 1, in: users)) // Пользователь: Алиса
print(findUser(byId: 3, in: users)) // Не найден (имя отсутствует)
print(findUser(byId: 9, in: users)) // Не найден

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

  1. «Телефонная книга» — реализуйте систему управления контактами с активным использованием опционалов:
struct Contact {
let name: String
var phone: String?
var email: String?
var company: String?
}

Реализуйте структуру PhoneBook со следующим функционалом:

  • add(_ contact: Contact) — добавить контакт.
  • find(byName name: String) -> Contact? — найти контакт по имени.
  • allEmails() -> [String] — массив всех email-адресов (без nil), используя compactMap.
  • contactsWithPhone() -> [Contact] — контакты, у которых заполнен телефон.
  • summary(for name: String) -> String — строка вида "Имя: X, Телефон: Y, Email: Z", где отсутствующие данные заменены на "не указано" (через ??).

Пример использования:

var book = PhoneBook()
book.add(Contact(name: "Анна", phone: "+79001234567", email: "anna@mail.ru", company: "TechCorp"))
book.add(Contact(name: "Борис", phone: nil, email: "boris@gmail.com", company: nil))
book.add(Contact(name: "Виктор", phone: "+79007654321", email: nil, company: nil))
print(book.find(byName: "Анна") as Any)
// Optional(Contact(name: "Анна", phone: Optional("+79001234567"), ...))
print(book.allEmails())
// ["anna@mail.ru", "boris@gmail.com"]
print(book.summary(for: "Виктор"))
// Имя: Виктор, Телефон: +79007654321, Email: не указано

Критерии оценивания

  • Корректность и полнота реализации: 0–5 баллов
  • Грамотное использование опционалов (if let, guard let, ??, optional chaining, compactMap): 0–5 баллов
  • Качество и читаемость кода (именование, отсутствие force unwrapping без необходимости): 0–3 балла
  • Наличие проверочных print/assert выражений: 0–3 балла

Максимум: 16 баллов. Бонус до +2 за дополнительные задания.


Дополнительно (по желанию)

  • В мини-проекте добавьте метод merge(_ other: PhoneBook), объединяющий две телефонные книги с устранением дубликатов по имени (при конфликте — сохранять запись, у которой заполнено больше полей).
  • Реализуйте функцию unwrapOrThrow<T>(_ optional: T?, error: String) throws -> T, которая бросает ошибку, если значение nil.
  • Сравните подходы к обработке отсутствия значений в Swift (Optional) и Python (None / исключения) — напишите короткий комментарий в коде.