Практика 12. Практика к лекции 11
Практика 12. Обобщения (Generics)
Цель: освоить обобщённое программирование в Swift — обобщённые функции, обобщённые типы, ограничения типов, ассоциированные типы, opaque-типы (some Protocol) и обобщённые расширения — через серию упражнений от базовых к проектным. Все задания выполняются на Linux в командной строке.
Рекомендации по выполнению:
- Создавайте файлы с расширением
.swiftи запускайте командойswift file.swift. - Каждое задание можно оформлять в отдельном файле или объединять логически близкие в один.
- Добавляйте
print-вызовы для демонстрации работы каждого задания. - Сравнивайте обобщения Swift с
typing.GenericиTypeVarв Python.
A. Разминка — обобщённые функции
- Напишите обобщённую функцию
filterItems<T>(_ items: [T], predicate: (T) -> Bool) -> [T], принимающую массив и замыкание-предикат, возвращающую массив элементов, удовлетворяющих условию.
Продемонстрируйте работу с разными типами:
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]let evens = filterItems(numbers) { $0 % 2 == 0 }print(evens) // [2, 4, 6, 8, 10]
let names = ["Алиса", "Борис", "Ал", "Вера", "Алексей"]let longNames = filterItems(names) { $0.count > 3 }print(longNames) // ["Алиса", "Борис", "Вера", "Алексей"]- Напишите обобщённую функцию
swapValues<T>(_ a: inout T, _ b: inout T), меняющую значения двух переменных местами. Напишите такжеfindIndex<T: Equatable>(of value: T, in array: [T]) -> Int?, возвращающую индекс первого вхождения элемента.
Пример ожидаемого поведения:
var x = 10, y = 20swapValues(&x, &y)print(x, y) // 20 10
let idx = findIndex(of: "Swift", in: ["Python", "Swift", "Go"])print(idx!) // 1B. Обобщённые типы
- Создайте структуру
Pair<A, B>с двумя свойствамиfirst: Aиsecond: B. Добавьте:- Метод
swapped() -> Pair<B, A>— возвращает пару с переставленными элементами. - Метод
map<C, D>(_ transformFirst: (A) -> C, _ transformSecond: (B) -> D) -> Pair<C, D>— трансформирует оба элемента.
- Метод
Пример ожидаемого поведения:
let pair = Pair(first: "Swift", second: 5)print(pair.first) // Swiftprint(pair.second) // 5
let swapped = pair.swapped()print(swapped.first) // 5print(swapped.second) // Swift
let mapped = pair.map({ $0.uppercased() }, { $0 * 10 })print(mapped.first) // SWIFTprint(mapped.second) // 50- Создайте обобщённую структуру
Stack<Element>с операциямиpush,pop,peek,isEmpty,count. Реализуйте внутреннее хранение на массиве.
Пример ожидаемого поведения:
var intStack = Stack<Int>()intStack.push(10)intStack.push(20)intStack.push(30)print(intStack.pop()!) // 30print(intStack.peek()!) // 20print(intStack.count) // 2
var strStack = Stack<String>()strStack.push("hello")strStack.push("world")print(strStack.pop()!) // worldC. Ограничения типов
- Напишите функцию
removeDuplicates<T: Hashable>(from array: [T]) -> [T], возвращающую массив без дубликатов с сохранением исходного порядка элементов.
Пример ожидаемого поведения:
let nums = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]print(removeDuplicates(from: nums)) // [3, 1, 4, 5, 9, 2, 6]
let words = ["кот", "пёс", "кот", "рыба", "пёс"]print(removeDuplicates(from: words)) // ["кот", "пёс", "рыба"]- Напишите обобщённую функцию
mostFrequent<T: Hashable>(in array: [T]) -> (element: T, count: Int)?, возвращающую элемент, встречающийся чаще всего, и его количество. Если массив пуст — вернутьnil.
Пример ожидаемого поведения:
let data = [1, 3, 2, 3, 1, 3, 2]if let result = mostFrequent(in: data) { print("\(result.element) встречается \(result.count) раз") // 3 встречается 3 раз}D. Ассоциированные типы
- Реализуйте протокол
Summableс:associatedtype Element: Numeric- Свойство
items: [Element] { get } - Метод
sum() -> Element
Реализуйте соответствие для:
struct IntCollection: Summable(сElement = Int)struct DoubleCollection: Summable(сElement = Double)
Пример ожидаемого поведения:
let ints = IntCollection(items: [1, 2, 3, 4, 5])print(ints.sum()) // 15
let doubles = DoubleCollection(items: [1.5, 2.5, 3.0])print(doubles.sum()) // 7.0- Создайте протокол
Containerс:associatedtype Itemmutating func append(_ item: Item)var count: Int { get }subscript(i: Int) -> Item { get }
Реализуйте struct Queue<T>: Container (FIFO-очередь) с операциями enqueue (через append), dequeue() -> T? и доступом по индексу.
Пример ожидаемого поведения:
var queue = Queue<String>()queue.append("первый")queue.append("второй")queue.append("третий")print(queue.count) // 3print(queue[0]) // первыйprint(queue.dequeue()!) // первыйprint(queue.count) // 2E. Обобщённые расширения
- Расширьте
Stack<Element>из задания 4, добавив:- Метод
filter(_ predicate: (Element) -> Bool) -> Stack<Element>— возвращает новый стек с элементами, удовлетворяющими предикату. - Метод
map<U>(_ transform: (Element) -> U) -> Stack<U>— трансформирует элементы стека. - Через условное расширение
where Element: Comparable— методmin() -> Element?, возвращающий минимальный элемент. - Через условное расширение
where Element: Numeric— методsum() -> Element. - Через условное расширение
where Element: CustomStringConvertible— методprettyPrint(), выводящий элементы стека в формате[bottom] el1 | el2 | el3 [top].
- Метод
Пример ожидаемого поведения:
var stack = Stack<Int>()stack.push(5)stack.push(2)stack.push(8)stack.push(1)
let filtered = stack.filter { $0 > 3 }print(filtered.pop()!) // 8print(filtered.pop()!) // 5
print(stack.min()!) // 1print(stack.sum()) // 16
stack.prettyPrint() // [bottom] 5 | 2 | 8 | 1 [top]
let mapped = stack.map { Double($0) * 1.5 }print(mapped.pop()!) // 1.5F. Мини-проект
- «Обобщённый кэш с политикой вытеснения» — создайте обобщённую структуру
Cache<Key: Hashable, Value>:
Требования:
- Инициализация с
capacity: Int(максимальное количество элементов). - Метод
set(_ key: Key, value: Value)— добавляет элемент; если кэш полон, удаляет самый старый элемент (FIFO). - Метод
get(_ key: Key) -> Value?— возвращает значение илиnil. - Метод
remove(_ key: Key)— удаляет элемент. - Вычисляемое свойство
count: Int. - Метод
allKeys() -> [Key]— возвращает все ключи в порядке добавления.
Добавьте условное расширение where Value: CustomStringConvertible с методом dump(), выводящим содержимое кэша в формате key: value.
Пример ожидаемого поведения:
var cache = Cache<String, Int>(capacity: 3)cache.set("a", value: 1)cache.set("b", value: 2)cache.set("c", value: 3)print(cache.get("b")!) // 2print(cache.count) // 3
cache.set("d", value: 4) // "a" вытеснен (FIFO)print(cache.get("a")) // nilprint(cache.allKeys()) // ["b", "c", "d"]cache.dump()// b: 2// c: 3// d: 4Дополнительно к мини-проекту: создайте протокол EvictionPolicy с associatedtype Key: Hashable и методами recordAccess(_ key: Key) и evict() -> Key?. Реализуйте FIFOPolicy и LRUPolicy. Параметризуйте Cache политикой вытеснения.
G. Критерии оценивания
- Корректность и полнота реализации: 0–5 баллов
- Грамотное использование обобщённых функций и типов: 0–4 балла
- Правильное применение ограничений типов, ассоциированных типов и условных расширений: 0–4 балла
- Читаемость кода и наличие демонстрационных примеров: 0–3 балла
Максимум: 16 баллов. Бонус до +2 за дополнительные задания.
H. Дополнительно (по желанию)
- Реализуйте обобщённый
BinarySearchTree<Element: Comparable>с операциямиinsert,contains,inOrderTraversal. - Создайте функцию с opaque return type:
func makeCollection() -> some Collectionи объясните, чемsome Collectionотличается отany Collection. - Сравните обобщения Swift с
typing.Generic[T]в Python — напишите аналогStack<Element>на Python с аннотациями типов и обсудите разницу в гарантиях компилятора.