swift

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

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

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

Прежде всего польза Set в том, что мы можем ответить на вопрос – “Хранится ли у нас такой элемент ?” – мгновенно.

Дело в том: чтобы проверить есть ли элемент в массиве, нам необходимо пройтись по каждому из элементу прежде, чем мы найдём нужный. И, скажем, если мы ищем букву “Я”, то нам придётся пройтись по всему алфавиту, прежде, чем мы убедимся, что такой буквы нет – это долго.

А представьте, что у нас массив из миллиона элементов, а нужный нам в самом конце ? – ужас.

В Set, благодаря тому, что он может хранить только Hashable, уникальные элементы – мы можем проверить наличие элемента мгновенно. (О Hashable мы поговорим позже)

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

Тема Описание
🎯 Основы Что такое 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)

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

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

// ❌ Плохо: используем массив для уникальных данных

// Представьте, что у нас есть группа в ТГ, очевидно, что один участник не может повторяться
// Мы можем в качестве хранилища выбрать Set – в таком случае защитимся наверняка от дубликатов
var userIdsArray: [Int] = [101, 102, 103, 101, 104, 102]

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

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

Создание 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)") // Осталось: ["🥛 Молоко", "🧀 Сыр", "🍎 Яблоко"]
}

Обратите внимание – чтобы удалить элемент из массива, нам нужно знать его индекс, но в Set нет индекса – мы моем сразу удалить элемент.

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

Обратите внимание – подписав нашу Car под Hashable, мы можем хранить Авто в сете, иначе не получится. С примитивными типами данных (String, Int, Bool…) всё проще – они уже под Hashable, чтобы хранить их в Set ничего дополнительно делать не нужно

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

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

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

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

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

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

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

let thisIsSet: Set<String> = ["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. Когда пользователь хочет вступить в ту или иную группу в ТГ – перед ним есть кнопка Вступить / Выйти

    Необходимо написать функцию, которая будет проверять есть ли данный пользователь в списке участников групп. И если он есть – функция должна возвращать текст Выйти, если пользователя нет в списке – Вступить

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

Далее

Словари