swift

Словарь (Dictionary) в Swift 📖

Dictionary — это неупорядоченная коллекция, которая хранит пары «ключ — значение»

До сих пор мы говорили о списках, как о наборе элементов, теперь поговорим о несколько ином виде хранилища – похожем на записную книгу.

В массиве (Array) у нас есть список элементов, для доступа к конкретному нужно знать его индекс.

Представьте, что у вас есть список номеров ваших женщин – Жены, дочери, мамы, тёти, бабушки и любовниц.

Довольно сложно понять, какой индекс кому соответствует, скажем – кто у вас под индексом 5 ? Можно и запутаться.

Словарь решает подобную проблему – у вас есть конкретное ключ-значение, например:

Телефонная книга содержит:
- "Батя"  "+7-912-345-67-89"
- "Папа"  "+7-915-678-90-12"
- "Отец"  "+7-903-123-45-67"

Каждый ключ в словаре уникален, а значения могут повторяться. Мы не сможем запить под ключом “Батя” несколько значений, если конечно не станем использовать массив, но даже в таком случае для “Бати” будет только один вариант массива.

Это значит, что для одинаковык ключей не может быть разных данных.

// Сравним уже известные вам Массив
let fruitsArray: [String] = [
	"Яблоко 🍎", 
	"Банан 🍌", 
	"Клубничка 🍓"
]

// И Словарь
let fruitsDictionary: [String: String] = [
	"apple": "Яблоко 🍎", 
	"banana": "Банан 🍌", 
	"strawberry": "Клубничка 🍓"
]

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

Тема Описание
Основы Что такое Dictionary и чем он отличается от массива
Применение Когда удобно использовать словарь вместо других коллекций
Создание Как объявлять и инициализировать Dictionary
Добавление Как добавлять новые пары (ключ-значение) в словарь
Получаем данные Почему словари всегда возвращают опциональные значения
Удаление Как удалять элементы из словаря
Доступ Как получать значения по ключу
Перебор Как проходить по всем элементам словаря
Изменение Как обновлять значения
Сложные типы Какие типы могут быть ключами

Что такое Dictionary? 🧐

Представьте, что у вас есть телефонная книга 📒:

Телефонная книга содержит:
- "Мама" 👩  "+7-912-345-67-89"
- "Папа" 👨  "+7-915-678-90-12"
- "Друг" 🧑  "+7-903-123-45-67"

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

  1. 🏷️ У каждого контакта уникальное имя (не может быть двух “Мама”)
  2. 🔑 По имени можно быстро найти номер (не нужно листать всю книгу)
  3. 📱 У каждого имени есть значение — номер телефона

Dictionary vs Array

Характеристика Массив (Array) Словарь (Dictionary)
Доступ Индекс (let item = array[2]) Ключ (let phone = dict["Мама"])
Тип индекса/ключа Только Int Любой Hashable тип (String, Int, и т.д.)
Дубликаты ✅ Разрешены ❌ Ключи уникальны, значения могут повторяться
Скорость поиска 🐢 Медленная (O(n)) ⚡ Мгновенная (O(1))
Аналогия Полка с книгами 📚 Телефонная книга 📒
Порядок элементов ✅ Сохраняется ❌ Не гарантируется

Пример в коде:

// Массив — ищем по позиции
let playlist = ["Песня 1", "Песня 2", "Песня 3"]
print(playlist[1])  // "Песня 2" (знаем индекс)

// Словарь — ищем по ключу
let phoneBook = [
    "Мама": "👩 +7-912-345-67-89", 
    "Папа": "👨 +7-915-678-90-12"
]
print(phoneBook["Мама"]!)  // "👩 +7-912-345-67-89" (знаем имя)

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

Ситуация Пример Почему Dictionary?
Поиск по идентификатору Найти пользователя по ID Мгновенный доступ по ключу
Настройки / Конфиги Параметры приложения Удобно хранить пары "volume": 44
Кэширование данных Временное хранение результатов Быстрый доступ по ключу
Подсчет частоты Сколько раз встречается слово Ключ — слово, значение — счетчик
API ответы JSON данные Прямая аналогия со структурой JSON

На первый взгляд, представленные выше примеры кажутся неочевидными.

Это связано с недостатком опыта, не волнуйтесь – по мере продвижения в мире программирования, вы прекрасно разберётесь в ситуациях, в которых без Словаря не обойтись.

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

// ❌ Плохо: используем массив для поиска по ID
var usersArray: [(id: Int, name: String)] = []
// Чтобы найти пользователя с id = 101, нужно перебрать весь массив

// ✅ Оптимально: используем словарь
var usersDict: [Int: String] = [
    101: "Анна", 
    102: "Иван", 
    103: "Ольга"
]
let userName = usersDict[101]  // Мгновенно! "Анна"

Создание Dictionary 🏗️

Пустой словарь

var emptyNumbers: [String: Double] = [:] // Пустой словарь [String: Double]

Ключ имеет формат Строка ("pi"), а хранимое по нему значение – Число с плавающей точкой (3.14)

Словарь с элементами

// Строковые ключи и строковые значения
var capitals: [String: String] = [
    "Россия": "Москва",
    "Франция": "Париж", 
    "Япония": "Токио"
]

// Целочисленные ключи и строковые значения
var httpStatus: [Int: String] = [
    200: "OK",
    404: "Not Found",
    500: "Internal Server Error"
]

// Без указания типа (Swift сам разберётся, не маленький уже)
let scores = [
    "Анна": 95, 
    "Иван": 87, 
    "Ольга": 92
]  // Тип: [String: Int]

Когда вы создаёте словарь с уже готовыми данными – Swift самостоятельно может определить тип данных, но если вы создаёте пустой словарь – вам нужно указать тип данных вручную var capitals: [String: String] = [:]

let vs var

// var — можно изменять
var mutableDict = ["a": 1, "b": 2]
mutableDict["c"] = 3  // ✅ Работает!

// let — нельзя изменять
let immutableDict = ["a": 1, "b": 2]
immutableDict["c"] = 3  // ❌ Ошибка!

Доступ к элементам 🔍

Как получить значение из словаря ? – нужно знать ключ

let fruitsDictionary = [
	"apple": "Яблоко 🍎", 
	"banana": "Банан 🍌", 
	"strawberry": "Клубничка 🍓"
]

Получение значения по ключу

// Простой доступ (возвращает опционал, так как, а вдруг элемента по ключу нет ?)
let strawberry = fruits["strawberry"] // Optional("Клубничка 🍓")
let mango = fruits["mango"]           // nil (такого ключа нет)

// С развертыванием опционала (Убедимся, что такой фрукт точно есть)
if let fruit = fruits["banana"] {
    print("Нашли: \(fruit)")  // Нашли: Банан 🍌
}

// Это модный запись – запомните его
// Если у нас в словаре нет значения для ключа "pear" – давайте вернём грушу, это же логично
let pear = fruits["pear", default: "Груша 🍐"]  // "Груша 🍐" (ключа нет, вернули default)

⚠️ Важно! Dictionary всегда возвращает опциональное значение, потому что ключа может не существовать!

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

var fruitsDictionary = [
	"apple": "Яблоко 🍎", 
	"banana": "Банан 🍌", 
	"strawberry": "Клубничка 🍓"
]

Добавление новой пары

fruitsDictionary["cherry"] = "Черешня 🍒" // Добавили ключ-значение для Черешни

Изменение существующего значения

fruitsDictionary["apple"] = "Тыблоко 🍏"  // Было "Яблоко 🍎", стало "Тыблоко 🍏"

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

Удаление по ключу

// Способ 1: присвоить nil
fruitsDictionary["banana"] = nil // Банана теперь нет

Очистка всего словаря

fruitsDictionary.removeAll() // [:]

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

let scores = [
"Анна": 95, 
"Иван": 87, 
"Петр": 78
]

Перебор по парам ключ-значение

Здесь – в качестве ключа для словаря мы использует имя, а в качестве значения – количество балло.

Вот такая запись позволяет нам пройтись прямо по паре ключ-значение:

for (name, score) in scores {
    print("\(name): \(score) баллов")
}
// Анна: 95 баллов
// Иван: 87 баллов
// Петр: 78 баллов

Перебор только ключей

for name in scores.keys {
    print("Студент: \(name)")
}
// Студент Анна
// Студент Иван
// Студент Ольга
// Студент Петр

Перебор только значений

for score in scores.values {
    print("Балл: \(score)")
}
// Балл 95
// Балл 87
// Балл 92
// Балл 78

Массивы из ключей и значений

Мы можем создать массив из ключей словаря или из его значений

let names = Array(scores.keys)        // ["Анна", "Иван", "Ольга", "Петр"]
let allScores = Array(scores.values)  // [95, 87, 92, 78]

Полезные свойства и методы 📚

var dict = ["a": 1, "b": 2, "c": 3]
Свойство Описание Пример
count Количество элементов dict.count // 3
isEmpty Проверка на пустоту dict.isEmpty // false
keys Все ключи dict.keys // [“a”, “b”, “c”]
values Все значения dict.values // [1, 2, 3]
first Первый элемент (не гарантирован) dict.first // Optional((“a”, 1))

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

Требование к ключам: Hashable

Чтобы тип мог быть ключом в словаре, он должен поддерживать протокол Hashable:

Это обязательное требование, запомните.

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

В качестве ключа, мы можем использовать создануню нами структуру

struct Person: Hashable {
    let id: Int
    let name: String
}

var personAges: [Person: Int] = [:]

let person1 = Person(id: 1, name: "Анна")
let person2 = Person(id: 2, name: "Иван")

personAges[person1] = 25
personAges[person2] = 30

print(personAges[person1])  // Optional(25)

Пример для хранение массива по ключу

Давайте представим, что у нас есть массив, в котором хранятся Персоны

let persons: [Person] = [
	Person(id: 0, name: "Василий"),
	Person(id: 1, name: "Пётр"),
	Person(id: 2, name: "Пётр"),
]

Перед нами поставили задачу – сгруппировать всех персон по имени – т.е. все Пети, должны быть собраны в одном месте, ровно как и все Василии и так далее. В таком случае нам идеально подойдёт Словарь.

В качестве ключа мы выберем имя персоны, а в качестве значения – массив, в котором будут храниться все персоны с таким именем.

///  Шаг 1. Создадим пустой словарь, где ключ – Строка, значение – массив Персон
var usersDictionary: [String: [Person]] = [:]

// Шаг 2. Наполним наш словарь данными
func createDickFromArray() {
	/// Давайте пройдёмся по всем персонам
    for person in persons {
        /// Важный момент!
        /// Если у нас в словаре уже есть массив из имён – работаем с ним
        if var array = usersDictionary[person.name] {
            /// Добавим тёзку к уже существующим
            array.append(person)
            /// Не забудем обновить наш словарь
            usersDictionary[person.name] = array
        } else {
            /// По данному ключу, ещё нет массива
            /// Создадим его! – это будет наша первая персона
            usersDictionary[person.name] = [person]
        }
    }
}

Шпаргалка 📝

Действие Код Пояснение
Создать пустой словарь var dict: [String: Int] = [:] Пустой словарь
С данными let dict = ["a": 1, "b": 2] С двумя парами
Получить значение dict["a"] Optional(1)
С значением по умолчанию dict["c", default: 0] 0 (если ключа нет)
Добавить/обновить dict["c"] = 3 Добавляет или обновляет
Удалить dict["a"] = nil Удаляет пару
Количество dict.count Сколько элементов
Проверить пустоту dict.isEmpty true, если пусто

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

Ошибка 1: Забывают про опционалы

let dict = ["a": 1, "b": 2]
let value = dict["c"] + 5  // ❌ Ошибка! value — опционал. Мы не можем сложить Optional(10) + 5

// ✅ Надо так:
if let value = dict["c"] {
    let result = value + 5 // В данном случае value – уже точно число, по значениею "c"
}

// Или так:
let result = (dict["c"] ?? 0) + 5

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

let dict = ["a": 1, "b": 2]
dict["c"] = 3 // ❌ Ошибка! let = неизменяемый

// ✅ Надо использовать var
var mutableDict = ["a": 1, "b": 2]
mutableDict["c"] = 3 // ✅ Работает!

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

let dict = ["a": 1, "b": 2, "c": 3, "d": 4]
// Не надейтесь, что элементы будут в порядке a, b, c, d!
for (key, value) in dict {
    print("\(key): \(value)")  // Может быть: c: 3, a: 1, d: 4, b: 2
}

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

Нужно Выбор Почему
Доступ по ключу Dictionary dict["ключ"] — мгновенно
Сохранить порядок Array У массива есть индексы
Уникальные ключи Dictionary Ключи всегда уникальны
Простой список Array Не нужны ключи
Связанные данные Dictionary Пары “ключ-значение”
Значение по индексу Array array[2] — легко

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

Вопрос 1

Что вернёт fruits["mango"], если такого ключа нет в словаре?

Вопрос 2

Как добавить новую пару в словарь?

Вопрос 3

Какой тип данных может быть ключом в словаре?

Задание 🚀

  1. Создайте структуру, в которой будет храниться информации о студентах и их оценках
  2. Добавьте 2-3 студента, значения оценок выберите сами
  3. Получите информацию о том, какие оценки у “Пети” и “Маши”
  4. Получи список всех ключей (Получится массив из студентов, в статье есть пример как это сделать)
  5. Со звёздочкой Посчитайте средний бал для каждого из студентов в словаре.

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

Далее

Функции