Лекция 5. Коллекции
1. Введение
Коллекции — один из ключевых инструментов любого языка программирования. Они позволяют хранить, группировать и обрабатывать наборы данных. В Swift существуют три основных типа коллекций: Array (массив), Set (множество) и Dictionary (словарь). В отличие от Python, коллекции Swift строго типизированы: вы не можете хранить элементы разных типов в одной коллекции без явного указания. В этой лекции мы подробно рассмотрим каждый тип, его операции и функциональные методы обработки данных.
2. Array — упорядоченный массив
Array — упорядоченная коллекция элементов одного типа. Прямой аналог list в Python, но с фиксированным типом элементов.
2.1. Создание массива
// Пустой массивvar numbers: [Int] = []var names = [String]() // альтернативный синтаксисvar zeros = Array(repeating: 0, count: 5) // [0, 0, 0, 0, 0]
// Массив с начальными значениямиlet fruits = ["яблоко", "банан", "вишня"]var scores = [95, 87, 72, 100, 64]Сравнение с Python:
numbers = [] # список может содержать любые типыnames = list()zeros = [0] * 5 # [0, 0, 0, 0, 0]
fruits = ["яблоко", "банан", "вишня"]scores = [95, 87, 72, 100, 64]2.2. Доступ по индексу
Индексация начинается с 0, как и в Python. Однако в Swift нет отрицательных индексов:
let fruits = ["яблоко", "банан", "вишня", "дыня"]
print(fruits[0]) // яблокоprint(fruits[2]) // вишня// print(fruits[-1]) // Ошибка! Swift не поддерживает отрицательные индексыprint(fruits[fruits.count - 1]) // дыня — аналог fruits[-1] в PythonВ Python:
fruits = ["яблоко", "банан", "вишня", "дыня"]print(fruits[-1]) # дыня — отрицательные индексы работают2.3. Добавление и удаление элементов
var cities = ["Москва", "Казань"]
// Добавлениеcities.append("Самара") // ["Москва", "Казань", "Самара"]cities.append(contentsOf: ["Уфа", "Омск"]) // добавить несколькоcities.insert("Пермь", at: 1) // вставка по индексу
// Удалениеlet removed = cities.remove(at: 0) // удаляет "Москва", возвращает еёcities.removeLast() // удаляет последний элементcities.removeAll() // очистить массивСравнение с Python:
cities = ["Москва", "Казань"]
cities.append("Самара")cities.extend(["Уфа", "Омск"])cities.insert(1, "Пермь")
cities.pop(0) # удаляет по индексу, возвращает элементcities.pop() # удаляет последнийcities.clear() # очистить2.4. Итерация по массиву
let languages = ["Swift", "Python", "Go", "Rust"]
// Простая итерацияfor lang in languages { print(lang)}
// С индексом — enumerated() аналогичен enumerate() в Pythonfor (index, lang) in languages.enumerated() { print("\(index): \(lang)")}В Python:
for lang in languages: print(lang)
for index, lang in enumerate(languages): print(f"{index}: {lang}")3. Полезные свойства и методы Array
Swift предоставляет богатый набор встроенных свойств и методов для массивов:
let numbers = [5, 3, 8, 1, 9, 2, 7]
// Свойстваprint(numbers.count) // 7 — количество элементов (len() в Python)print(numbers.isEmpty) // false (аналог: not numbers или len(numbers) == 0)print(numbers.first) // Optional(5) — первый элемент (Optional!)print(numbers.last) // Optional(7) — последний элемент
// Поискprint(numbers.contains(8)) // trueprint(numbers.firstIndex(of: 9)) // Optional(4)print(numbers.min()) // Optional(1)print(numbers.max()) // Optional(9)
// Порядокprint(numbers.sorted()) // [1, 2, 3, 5, 7, 8, 9] — новый массивprint(numbers.reversed()) // ReversedCollection — ленивая коллекцияprint(Array(numbers.reversed())) // [7, 2, 9, 1, 8, 3, 5] — явное преобразование
// Перемешиваниеprint(numbers.shuffled()) // случайный порядок (новый массив)Обратите внимание: first, last, min(), max(), firstIndex(of:) возвращают Optional, потому что массив может быть пустым. Это фундаментальное отличие от Python, где обращение к пустому списку вызывает исключение.
4. Set — множество
Set — неупорядоченная коллекция уникальных элементов. Прямой аналог set в Python. Элементы должны реализовывать протокол Hashable (аналог __hash__ в Python).
4.1. Создание множества
// Пустое множествоvar tags = Set<String>()
// Множество с начальными значениямиlet colors: Set<String> = ["красный", "зелёный", "синий"]let primes: Set = [2, 3, 5, 7, 11] // тип выводится автоматическиВ Python:
tags = set()colors = {"красный", "зелёный", "синий"}primes = {2, 3, 5, 7, 11}Важно: в Swift литерал множества использует тот же синтаксис [], что и массив. Различие задаётся аннотацией типа Set<T>.
4.2. Основные операции
var skills: Set = ["Swift", "Python", "Git"]
skills.insert("Docker") // (.inserted: true, ...)skills.insert("Swift") // (.inserted: false, ...) — уже естьskills.remove("Git") // Optional("Git")print(skills.contains("Python")) // trueprint(skills.count) // 34.3. Операции над множествами
Это одна из главных причин использования Set. Swift предоставляет те же математические операции, что и Python:
let frontend: Set = ["HTML", "CSS", "JavaScript", "TypeScript"]let backend: Set = ["Python", "Go", "JavaScript", "TypeScript"]
// Объединение (union) — все элементы из обоих множествlet all = frontend.union(backend)// {"HTML", "CSS", "JavaScript", "TypeScript", "Python", "Go"}
// Пересечение (intersection) — общие элементыlet common = frontend.intersection(backend)// {"JavaScript", "TypeScript"}
// Разность (subtracting) — элементы первого, которых нет во второмlet onlyFrontend = frontend.subtracting(backend)// {"HTML", "CSS"}
// Симметрическая разность — элементы, которые есть только в одном из множествlet exclusive = frontend.symmetricDifference(backend)// {"HTML", "CSS", "Python", "Go"}Сравнение с Python:
frontend = {"HTML", "CSS", "JavaScript", "TypeScript"}backend = {"Python", "Go", "JavaScript", "TypeScript"}
all_langs = frontend | backend # unioncommon = frontend & backend # intersectiononly_frontend = frontend - backend # differenceexclusive = frontend ^ backend # symmetric_difference4.4. Отношения между множествами
let a: Set = [1, 2, 3, 4, 5]let b: Set = [2, 3]let c: Set = [6, 7]
print(b.isSubset(of: a)) // true — b подмножество aprint(a.isSuperset(of: b)) // true — a надмножество bprint(a.isDisjoint(with: c)) // true — нет общих элементов5. Dictionary — словарь
Dictionary — неупорядоченная коллекция пар «ключ–значение». Аналог dict в Python. Ключи должны быть Hashable.
5.1. Создание словаря
// Пустой словарьvar userInfo: [String: String] = [:]var scores = [String: Int]()
// Словарь с начальными значениямиlet capitals = [ "Россия": "Москва", "Франция": "Париж", "Германия": "Берлин"]В Python:
user_info = {}scores = dict()
capitals = { "Россия": "Москва", "Франция": "Париж", "Германия": "Берлин",}5.2. Доступ к элементам
Доступ по ключу возвращает Optional — ключа может не быть:
let capitals = ["Россия": "Москва", "Франция": "Париж"]
print(capitals["Россия"]) // Optional("Москва")print(capitals["Италия"]) // nilprint(capitals["Италия"] ?? "Неизвестно") // "Неизвестно" — значение по умолчаниюВ Python:
print(capitals["Россия"]) # "Москва"# print(capitals["Италия"]) # KeyError!print(capitals.get("Италия", "Неизвестно")) # "Неизвестно"5.3. Добавление, изменение и удаление
var ages = ["Анна": 25, "Борис": 30]
// Добавление / изменениеages["Виктор"] = 28 // добавитьages["Анна"] = 26 // обновить
// Метод updateValue — возвращает старое значениеlet old = ages.updateValue(31, forKey: "Борис")print(old) // Optional(30)
// Удалениеages["Виктор"] = nil // удаление через присвоение nilages.removeValue(forKey: "Борис")5.4. Итерация по словарю
let population = ["Москва": 13_000_000, "Казань": 1_300_000, "Самара": 1_100_000]
// По парам (ключ, значение)for (city, pop) in population { print("\(city): \(pop) чел.")}
// Только ключиfor city in population.keys { print(city)}
// Только значенияfor pop in population.values.sorted() { print(pop)}6. Типизированные коллекции
В Python списки могут содержать элементы любых типов:
mixed = [1, "hello", 3.14, True, [1, 2]] # допустимоВ Swift каждая коллекция хранит элементы строго одного типа:
let numbers: [Int] = [1, 2, 3]// numbers.append("hello") // Ошибка компиляции! Ожидается Int
let names: [String] = ["Анна", "Борис"]let flags: Set<Bool> = [true, false]let ages: [String: Int] = ["Анна": 25]Тип элементов может выводиться автоматически:
let items = [10, 20, 30] // компилятор выводит [Int]let words = ["один", "два"] // компилятор выводит [String]Если вам действительно нужна коллекция с элементами разных типов, Swift предлагает Any, но это крайне не рекомендуется:
let mixed: [Any] = [1, "hello", 3.14] // работает, но теряется типобезопасность7. Изменяемость коллекций: var vs let
В Swift изменяемость коллекции определяется ключевым словом при объявлении:
var mutableArray = [1, 2, 3]mutableArray.append(4) // OK
let immutableArray = [1, 2, 3]// immutableArray.append(4) // Ошибка компиляции! let = константаЭто распространяется на все коллекции — Array, Set, Dictionary:
let fixedSet: Set = [1, 2, 3]// fixedSet.insert(4) // Ошибка!
let fixedDict = ["a": 1]// fixedDict["b"] = 2 // Ошибка!В Python list, set, dict всегда изменяемы. Для неизменяемых аналогов используются tuple, frozenset; неизменяемого dict в стандартной библиотеке нет. В Swift подход единообразен: let делает любую коллекцию неизменяемой.
8. Функциональные методы коллекций
Swift предоставляет мощный набор функциональных методов для обработки коллекций. Эти методы активно используют замыкания, изученные в предыдущей лекции.
8.1. map — преобразование каждого элемента
map применяет замыкание к каждому элементу и возвращает новый массив:
let numbers = [1, 2, 3, 4, 5]let doubled = numbers.map { $0 * 2 }print(doubled) // [2, 4, 6, 8, 10]
let names = ["анна", "борис", "виктор"]let capitalized = names.map { $0.uppercased() }print(capitalized) // ["АННА", "БОРИС", "ВИКТОР"]В Python:
doubled = list(map(lambda x: x * 2, numbers))# или идиоматичнее:doubled = [x * 2 for x in numbers]8.2. filter — отбор элементов по условию
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]let evens = numbers.filter { $0 % 2 == 0 }print(evens) // [2, 4, 6, 8, 10]
let words = ["Swift", "Go", "Rust", "Python", "C"]let longWords = words.filter { $0.count > 3 }print(longWords) // ["Swift", "Rust", "Python"]В Python:
evens = list(filter(lambda x: x % 2 == 0, numbers))# или:evens = [x for x in numbers if x % 2 == 0]8.3. reduce — свёртка в одно значение
reduce принимает начальное значение и замыкание, последовательно комбинирующее результат с каждым элементом:
let numbers = [1, 2, 3, 4, 5]let sum = numbers.reduce(0) { $0 + $1 }print(sum) // 15
// Можно ещё короче — передать оператор как функциюlet product = numbers.reduce(1, *)print(product) // 120В Python:
from functools import reducetotal = reduce(lambda acc, x: acc + x, numbers, 0)# или просто:total = sum(numbers)8.4. forEach — выполнение действия для каждого элемента
В отличие от map, не возвращает новый массив:
let names = ["Анна", "Борис", "Виктор"]names.forEach { name in print("Привет, \(name)!")}8.5. compactMap — преобразование с отбрасыванием nil
compactMap работает как map, но автоматически исключает nil-результаты:
let strings = ["1", "два", "3", "четыре", "5"]let numbers = strings.compactMap { Int($0) }print(numbers) // [1, 3, 5] — «два» и «четыре» не преобразовались, дали nilВ Python для подобного нужно два шага:
strings = ["1", "два", "3", "четыре", "5"]numbers = []for s in strings: try: numbers.append(int(s)) except ValueError: pass# numbers == [1, 3, 5]8.6. flatMap — разворачивание вложенных коллекций
flatMap объединяет вложенные массивы в один:
let nested = [[1, 2, 3], [4, 5], [6, 7, 8, 9]]let flat = nested.flatMap { $0 }print(flat) // [1, 2, 3, 4, 5, 6, 7, 8, 9]В Python:
nested = [[1, 2, 3], [4, 5], [6, 7, 8, 9]]flat = [x for sub in nested for x in sub]# [1, 2, 3, 4, 5, 6, 7, 8, 9]8.7. sorted(by:) — сортировка с пользовательским порядком
let names = ["Виктор", "Анна", "Дарья", "Борис"]
// По алфавиту (по умолчанию)print(names.sorted()) // ["Анна", "Борис", "Виктор", "Дарья"]
// В обратном порядкеprint(names.sorted(by: >)) // ["Дарья", "Виктор", "Борис", "Анна"]
// По длине строкиprint(names.sorted { $0.count < $1.count })// ["Анна", "Борис", "Дарья", "Виктор"]9. Сравнение с list comprehensions Python
В Python для преобразования и фильтрации часто используют list comprehensions — компактный и идиоматичный синтаксис. В Swift аналогичная выразительность достигается через цепочки функциональных методов.
Задача: из списка чисел от 1 до 10 выбрать чётные, возвести их в квадрат и получить результат.
Python:
result = [x ** 2 for x in range(1, 11) if x % 2 == 0]# [4, 16, 36, 64, 100]Swift:
let result = (1...10) .filter { $0 % 2 == 0 } .map { $0 * $0 }print(result) // [4, 16, 36, 64, 100]Ещё один пример — словарь из массива:
Python:
words = ["hello", "world", "swift"]lengths = {w: len(w) for w in words}# {"hello": 5, "world": 5, "swift": 5}Swift:
let words = ["hello", "world", "swift"]let lengths = Dictionary(uniqueKeysWithValues: words.map { ($0, $0.count) })print(lengths) // ["hello": 5, "world": 5, "swift": 5]10. Цепочки функциональных вызовов (Chaining)
Одно из главных преимуществ функциональных методов — возможность выстраивать цепочки преобразований. Каждый метод возвращает новую коллекцию, к которой применяется следующий метод:
struct Student { let name: String let grade: Int}
let students = [ Student(name: "Анна", grade: 85), Student(name: "Борис", grade: 92), Student(name: "Виктор", grade: 67), Student(name: "Дарья", grade: 95), Student(name: "Елена", grade: 73),]
// Получить имена отличников (grade >= 90), отсортированные по алфавитуlet honors = students .filter { $0.grade >= 90 } .sorted { $0.name < $1.name } .map { $0.name }
print(honors) // ["Борис", "Дарья"]Эквивалент в Python:
honors = sorted( [s.name for s in students if s.grade >= 90])# ["Борис", "Дарья"]Более сложный пример — обработка текстовых данных:
let text = "Swift — это мощный и выразительный язык программирования"
let longWords = text .split(separator: " ") // разделить на слова .map { String($0) } // преобразовать Substring -> String .filter { $0.count > 3 } // оставить длинные слова .sorted() // отсортировать
print(longWords)// ["Swift", "выразительный", "мощный", "программирования", "язык", "это"]11. Сводная таблица: Swift vs Python
| Операция | Swift | Python |
|---|---|---|
| Создание массива | [1, 2, 3] или Array<Int>() | [1, 2, 3] или list() |
| Создание множества | Set([1, 2, 3]) | {1, 2, 3} или set() |
| Создание словаря | ["a": 1] или [String: Int]() | {"a": 1} или dict() |
| Типизация | Строгая, элементы одного типа | Динамическая, любые типы |
| Неизменяемость | let для любой коллекции | tuple, frozenset (нет для dict) |
| Количество элементов | .count | len() |
| Проверка пустоты | .isEmpty | not collection или len() == 0 |
| Доступ по ключу | Возвращает Optional | KeyError или .get() |
| Отрицательные индексы | Не поддерживаются | list[-1] |
| map | .map { ... } | map() / list comprehension |
| filter | .filter { ... } | filter() / list comprehension |
| reduce | .reduce(init) { ... } | functools.reduce() |
| compactMap | .compactMap { ... } | Нет прямого аналога |
| flatMap | .flatMap { ... } | Вложенный comprehension |
| Сортировка | .sorted() / .sorted(by:) | sorted() / sorted(key=) |
| Объединение множеств | .union() | | или .union() |
| Пересечение множеств | .intersection() | & или .intersection() |
12. Упражнения
Упражнение 1. Создайте массив целых чисел от 1 до 20. Используя filter и reduce, найдите сумму всех чисел, которые делятся на 3.
// Ожидаемый результат: 3 + 6 + 9 + 12 + 15 + 18 = 63Упражнение 2. Дан массив строк — названия городов. Используя map и sorted, получите массив строк вида "ГОРОД (N букв)", отсортированный по длине названия.
let cities = ["Москва", "Уфа", "Новосибирск", "Казань", "Сочи"]// Ожидаемый результат:// ["УФА (3 букв)", "СОЧИ (4 букв)", "КАЗАНЬ (6 букв)", "МОСКВА (6 букв)", "НОВОСИБИРСК (11 букв)"]Упражнение 3. Дано два множества Set<String> — навыки двух программистов. Найдите: (а) общие навыки, (б) уникальные навыки каждого, (в) все навыки вместе.
let dev1: Set = ["Swift", "Python", "Git", "SQL", "Docker"]let dev2: Set = ["Python", "JavaScript", "Git", "React", "Docker"]Упражнение 4. Создайте словарь [String: [Int]], где ключ — имя студента, значение — массив оценок. Используя map и reduce, создайте новый словарь [String: Double] со средними оценками.
let grades = [ "Анна": [90, 85, 92, 88], "Борис": [70, 65, 80, 75], "Виктор": [95, 100, 92, 98]]// Ожидаемый результат: ["Анна": 88.75, "Борис": 72.5, "Виктор": 96.25]Упражнение 5. Дан массив опциональных строк. Используя compactMap, извлеките непустые значения и преобразуйте их в верхний регистр.
let input: [String?] = ["swift", nil, "python", nil, "go", nil]// Ожидаемый результат: ["SWIFT", "PYTHON", "GO"]Упражнение 6. Напишите цепочку функциональных вызовов, которая из строки текста извлекает все слова длиной больше 4 символов, убирает дубликаты и возвращает отсортированный массив в нижнем регистре.
let text = "Swift это Swift язык для разработки приложений для Apple и Linux"// Ожидаемый результат: ["apple", "linux", "swift", "разработки", "приложений"]13. Вопросы для самопроверки
- Чем
Arrayв Swift отличается отlistв Python с точки зрения типизации? - Почему
firstиlastу массива возвращаютOptional, а не просто значение? - Какая разница между
varиletпри объявлении коллекции? - Перечислите четыре основные операции над множествами в Swift. Какие у них аналоги в Python?
- Почему доступ к словарю по ключу возвращает
Optional? Как указать значение по умолчанию? - Чем
compactMapотличается отmap? Приведите пример. - Чем
flatMapотличается отmap? Когда его следует использовать? - Как в Swift реализовать аналог Python-выражения
[x ** 2 for x in range(10) if x % 2 == 0]? - Почему подход Swift (строгая типизация коллекций) безопаснее, чем динамические списки Python?
- Что возвращает
reduceи зачем ему нужно начальное значение?
14. Итоги
В этой лекции мы изучили:
- Array — упорядоченный типизированный массив: создание, индексация, добавление и удаление элементов, итерация, полезные свойства (
count,isEmpty,first,last) и методы (contains,sorted,reversed). - Set — неупорядоченное множество уникальных элементов с мощными операциями:
union,intersection,subtracting,symmetricDifference. - Dictionary — коллекция пар «ключ–значение» с безопасным доступом через
Optional. - Типизация коллекций — строгая типизация как ключевое отличие от Python.
- Изменяемость — единообразный подход через
var/let. - Функциональные методы —
map,filter,reduce,compactMap,flatMap,forEach,sorted(by:)и их сравнение с Python. - Цепочки вызовов — мощный инструмент декларативной обработки данных.
В следующей лекции мы рассмотрим Optionals — уникальный механизм Swift для безопасной работы с отсутствием значений, который тесно связан с типизированными коллекциями и методами вроде compactMap.