swift

Множества (Set) в Swift 🧰

Set — это неупорядоченная коллекция уникальных элементов одного типа.

// Сравним, уже известный вам Массив и Сет:
let fruits = ["🍎", "🍌", "🍎"]                  // ["🍎", "🍌", "🍎"] (дубликат есть)
let fruitsSet: Set<String> = ["🍎", "🍌", "🍎"]  // ["🍎", "🍌"] (дубликат удален)

Что вы изучите 🎯

Тема Описание
🎯 Основы Что такое Set и чем он отличается от массива
🤔 Применение Когда удобно использовать Set вместо других коллекций
🏗️ Создание Как объявлять и инициализировать Set
Добавление Как вставлять элементы в Set
🗑️ Удаление Как удалять элементы из Set
🔍 Проверка Как быстро проверить наличие элемента
🔄 Перебор Как проходить по элементам в Set
🧩 Сложные типы Как хранить в Set структуры и классы
🧮 Операции Математика множеств: пересечения, объединения

Что такое Set? 🧐

Представьте, что у вас есть ящик с инструментами 🧰:

Ящик содержит: [🔧, 🔨, 🪛, 🔩]

Главные правила такого ящика:

  1. 🏷️ Все инструменты должны быть одного типа (нельзя смешать гаечные ключи и яблоки)
  2. 🔀 Инструменты лежат вперемешку (именно поэтому у в Сете нет индексов)
  3. 🚫 Нет дубликатов (не может быть двух одинаковых молотков)

Set vs Array

Характеристика Массив (Array) Множество (Set)
Порядок ✅ Есть (Индексы) ❌ Случайный
Индексы ✅ Есть (array[2]) ❌ Нет
Дубликаты ✅ Есть ❌ Нет
Скорость поиска 🐢 Медленная (проверяет все) ⚡ Мгновенная
Аналогия Полка с книгами 📚 Ящик с инструментами 🧰
// Массив — порядок важен
let playlist = ["Песня 1", "Песня 2", "Песня 3"]
print(playlist[1])  // Всегда "Песня 2"

// Множество — порядок не важен
let uniqueTracks: Set = ["Песня 1", "Песня 2", "Песня 1"]
print(uniqueTracks)  // ["Песня 2", "Песня 1"] (порядок случаен, дубликат удалён)

Когда использовать Set 👍

Идеальные случаи для Set:

Ситуация Пример Почему Set?
Уникальные ID ID пользователей, заказов Автоматически удаляет дубликаты
Проверка наличия Есть ли товар на складе? Мгновенный поиск (contains)
Теги / Категории Хэштеги, Категории товаров Нет повторов, быстрый поиск
Математические операции Общие друзья, пересечение аудиторий Встроенные методы (intersection, union)

⚠️ Запомните: Преимущество Сета – моментальная проверка наличия элемента, только уникальные элементы

Пример из жизни:

// ❌ Плохо: используем массив для уникальных данных
var userIdsArray: [Int] = [101, 102, 103, 101, 104, 102]
// Нужно писать доп. код для удаления дубликатов

// ✅ Оптимально: используем Set
var userIdsSet: Set<Int> = [101, 102, 103, 101, 104, 102]
// Уже [101, 102, 103, 104] — дубликаты удалены автоматически, а доступ к ним моментален

⚠️ Запомните: ещё раз Преимущество Сета – моментальная проверка наличия элемента, только уникальные элементы

Создание Set 🏗️

Пустое множество

// Способ 1: с указанием типа
var emptyStrings: Set<String> = [] // Пустое множество строк
var emptyNumbers: Set<Int> = []    // Пустое множество чисел

Множество с элементами

// Swift сам понимает тип
var fruits: Set = ["Яблоко", "Мандарин", "Манго"] // Set<String>
var scores: Set = [5, 4, 5, 3, 5]                 // Set<Int> → [3, 4, 5]

// С явным указанием типа (если не указать явно – Swift подумает, что это Массив, вот он тупой)
var explicitSet: Set<Int> = [1, 2, 3, 3, 3]  // [1, 2, 3]

🧠 Важно! Даже если вы укажете дубликаты при создании, Set автоматически их удалит:

let setWithDuplicates = [1, 1, 2, 2, 3, 3] // [2, 3, 1] (порядок случаен, дубликатов нет (ещё раз повторим))

let vs var

// var — можно изменять
var mutableSet: Set = [1, 2, 3]
mutableSet.insert(4)  // ✅ Работает!

// let — нельзя изменять
let immutableSet: Set = [1, 2, 3]
immutableSet.insert(4)  // ❌ Ошибка!

Добавление элементов ➕

var fruits: Set = ["🍎 Яблоко", "🍌 Бананчек"]

// Добавляем новый элемент (через insert, append есть только у Массива)
fruits.insert("🍊 Мандарин")
print("Стало: \(fruits)")  // ["🍎 Яблоко", "🍌 Бананчек", "🍊 Мандарин"] (порядок случаен)

Удаление элементов 🗑️

var shopping: Set = ["🥛 Молоко", "🍞 Хлеб", "🧀 Сыр", "🍎 Яблоко"]

remove(_:) — удалить конкретный элемент

if let removed = shopping.remove("🍞 Хлеб") {
    print("Удалили: \(removed)")   // Удалили: 🍞 Хлеб
    print("Осталось: \(shopping)") // Осталось: ["🥛 Молоко", "🧀 Сыр", "🍎 Яблоко"]
}

removeAll() — очистить всё

shopping.removeAll()
print(shopping)  // []

filter — отфильтровать элементы по условию (создаёт копию с Массивом)

В массиве останутся, только те элементы, которые прошли проверку в .filter...

let numbers: Set = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let evenNumbersArray = numbers.filter { number in
	number % 2 == 0 // Остаток от деления, нужны только чётные значения (4 % 2 = 0 / 5 % 2 = 1)
}
// evenNumbersArray — это [Int], а не Set<Int> – Обратите внимание

// Превращаем массив обратно в Set
let evenNumbersSet = Set(evenNumbersArray)

⚠️ Запомните: Превратив массив в сет, вы моментально избавитесь от дубликатов

Проверка наличия 🔍

let availableColors: Set = ["🔴 Красный", "🟢 Зеленый", "🔵 Синий", "🟡 Желтый"]

// contains() — проверяет наличие
let hasRed = availableColors.contains("🔴 Красный")  // true
let hasBlack = availableColors.contains("⚫ Черный") // false

print("Красный есть? \(hasRed)")  // Красный есть? true
print("Черный есть? \(hasBlack)") // Черный есть? false

Важно! contains в Set работает мгновенно, а в Array — медленно:

Перебор элементов 🔄

let colors: Set = ["🔴", "🟢", "🔵", "🟡"]

// Простой перебор
for color in colors {
    print("Цвет: \(color)")
}
// Цвет: 🔵
// Цвет: 🟢
// Цвет: 🔴
// Цвет: 🟡

⚠️ Запомните: Порядок может быть другим при каждом запуске (Элементы в Сете хранятся в случайно порядке)

Сложные типы в Set 🧱

Требование: Hashable

Чтобы Set мог проверять уникальность, тип должен поддерживать протокол Hashable.

Данные типы данных автоматически поддерживают Hashable:

Как реализовать протокол Hashable самостоятельно, мы поговорим позже

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

struct Car: Hashable {
    let brand: String
    let model: String
    let year: Int
}

var cars: Set<Car> = []

let car1 = Car(brand: "BMW", model: "X5", year: 2020)
let car2 = Car(brand: "Audi", model: "Q7", year: 2021)
let car3 = Car(brand: "BMW", model: "X5", year: 2020)  // Полная копия car1

cars.insert(car1)
cars.insert(car2)
cars.insert(car3)  // Не добавился (уже есть car1)

print("Уникальных машин: \(cars.count)")  // 2

Математические операции над множествами 🧮

Это главная суперсила Set!

Используется не часто, хорошо просто знать, что так можно.

let swiftDevelopers: Set = ["Анна", "Иван", "Ольга", "Петр"]
let kotlinDevelopers: Set = ["Иван", "Петр", "Елена", "Дмитрий"]

🔄 Пересечение (intersection)

Элементы, которые есть и там, и там

let bothLanguage = swiftDevelopers.intersection(kotlinDevelopers)
print("Знают оба языка: \(bothLanguage)")  // ["Иван", "Петр"]

🔗 Объединение (union)

Все элементы из обоих множеств, без повторов

let allDevelopers = swiftDevelopers.union(kotlinDevelopers)
print("Все разработчики: \(allDevelopers)")
// ["Анна", "Иван", "Ольга", "Петр", "Елена", "Дмитрий"]

➖ Разность (subtracting)

Элементы из первого множества, которых нет во втором

let onlySwift = swiftDevelopers.subtracting(kotlinDevelopers)
print("Знают только Swift: \(onlySwift)")  // ["Анна", "Ольга"]

🎯 Симметричная разность (symmetricDifference)

Элементы, которые есть только в одном из множеств

let exclusive = swiftDevelopers.symmetricDifference(kotlinDevelopers)
print("Знают только один язык: \(exclusive)")
// ["Анна", "Ольга", "Елена", "Дмитрий"]

📊 Проверка отношений

let groupA: Set = [1, 2, 3]
let groupB: Set = [1, 2, 3, 4, 5]
let groupC: Set = [6, 7, 8]

// Подмножество (все элементы A есть в B?)
print(groupA.isSubset(of: groupB))     // true

// Надмножество (B содержит все элементы A?)
print(groupB.isSuperset(of: groupA))   // true

// Не пересекаются (нет общих элементов?)
print(groupA.isDisjoint(with: groupC)) // true

Шпаргалка 📝

Действие Код Пояснение
Создать пустой сет var set: Set<Int> = [] Пустое множество чисел
С данными let set: Set = [1, 2, 3] Из трех чисел
Добавить set.insert(4) Добавляет 4
Удалить set.remove(2) Удаляет 2
Удалить все set.removeAll() Очищает
Проверить наличие set.contains(3) true, если есть 3
Количество set.count Сколько элементов
Проверить пустоту set.isEmpty true, если пусто

Частые ошибки новичков ⚠️

Ошибка 1: Забывают указать Set при создании

// ❌ Это массив, а не множество
let wrong = [1, 2, 3, 3, 3]  // [Int] — массив с дубликатами

// ✅ Это множество
let correct: Set = [1, 2, 3, 3, 3]  // Set<Int> — без дубликатов

Ошибка 2: Пытаются обратиться по индексу

let thisIsSet: Set = ["a", "b", "c"]
let element = thisIsSet[1]  // ❌ Ошибка! У Set нет индексов

// ✅ Надо перебирать или использовать .contains
if thisIsSet.contains("b") {
    print("b есть в множестве")
}

Ошибка 3: Пытаются изменить let-множество

let thisIsSet: Set = [1, 2, 3]
thisIsSet(4)  // ❌ Ошибка! let = неизменяемое

// ✅ Надо использовать var
var mutableSet: Set = [1, 2, 3]
mutableSet.insert(4)  // ✅ Работает!

Ошибка 4: Думают, что порядок сохранится

let set: Set = [1, 2, 3, 4, 5]
// Не надейтесь, что элементы будут в порядке 1,2,3,4,5!
for item in set {
    print(item)  // Может быть: 3, 5, 1, 4, 2
}

Когда что использовать? 🤔

Нужно Выбор Почему
Сохранить порядок Array У массива есть индексы
Быстрый поиск Set contains работает мгновенно
Уникальные значения Set Автоматически удаляет дубликаты
Дубликаты разрешены Array Массив разрешает дубликаты
Математические операции Set intersection, union и т.д.
Доступ по индексу Array array[2] — легко!

Проверь себя! ✍️

Вопрос 1

Что будет в множестве после выполнения кода?

let thisIsSet: Set = [1, 2, 2, 3, 3, 3]
print(thisIsSet)

Вопрос 2

Можно ли получить элемент по индексу из Set?

Вопрос 3

Что вернет set.contains() если элемента нет?

Вопрос 4

Какая операция найдет общие элементы двух множеств?

Задание 🤔

  1. Создай тип данных TGUser
  2. В нём должна быть информация о телефонном номере, никнейме и роли пользователя (admin, user, guest)
  3. Роль пользователя это enum. Подумай – почему для роли удобно выбрать enum, а не просто String
  4. Создай список со всеми пользователями телеграмма
  5. Представь, что есть группа в телеграмме , в которую может вступить пользователь, у этой группы есть список участников. В группе не должно быть одинаковых участников
  6. Выбери правильное хранилище для списка пользователей в группе и добавь пару участников
  7. Когда пользователь хочет вступить в ту или иную группу в ТГ – перед ним есть кнопка Вступить / Выйти Необходимо написать функцию, которая будет проверять есть ли данный в пользователь в списке участников групп. И если он есть – функция должна возвращать текст Выйти, если пользователя нет в списке – Вступить

Решение можете присылать сюда

Что дальше? 🚀

Поздравляю! Вы освоили Set (Набор) — фундаментальную структуру данных. Теперь вы готовы к изучению:

Тема Описание
Словари Хранят пары “ключ-значение”
Массив Как массивы, но без порядка и без повторов