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

Лекция 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() в Python
for (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)) // true
print(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")) // true
print(skills.count) // 3

4.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 # union
common = frontend & backend # intersection
only_frontend = frontend - backend # difference
exclusive = frontend ^ backend # symmetric_difference

4.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 подмножество a
print(a.isSuperset(of: b)) // true — a надмножество b
print(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["Италия"]) // nil
print(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 // удаление через присвоение nil
ages.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 reduce
total = 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

ОперацияSwiftPython
Создание массива[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)
Количество элементов.countlen()
Проверка пустоты.isEmptynot collection или len() == 0
Доступ по ключуВозвращает OptionalKeyError или .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. Вопросы для самопроверки

  1. Чем Array в Swift отличается от list в Python с точки зрения типизации?
  2. Почему first и last у массива возвращают Optional, а не просто значение?
  3. Какая разница между var и let при объявлении коллекции?
  4. Перечислите четыре основные операции над множествами в Swift. Какие у них аналоги в Python?
  5. Почему доступ к словарю по ключу возвращает Optional? Как указать значение по умолчанию?
  6. Чем compactMap отличается от map? Приведите пример.
  7. Чем flatMap отличается от map? Когда его следует использовать?
  8. Как в Swift реализовать аналог Python-выражения [x ** 2 for x in range(10) if x % 2 == 0]?
  9. Почему подход Swift (строгая типизация коллекций) безопаснее, чем динамические списки Python?
  10. Что возвращает 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.