1. Навіщо вам Dictionary
Коли ви лише знайомитеся з колекціями, масив здається універсальним рішенням: розклали елементи один за одним — і живете спокійно. Але в реальному коді швидко постає запитання: «Як мені швидко знайти значення за певною ознакою?». Якщо щоразу переглядати весь масив, код починає нагадувати людину, яка шукає ключі в квартирі й послідовно перевіряє кожну полицю.
Уявіть задачу: у нас є список книг і кількість сторінок, які ми прочитали. У масиві це могло б виглядати як два масиви: назви окремо, сторінки окремо. Або як масив пар. І ось вам потрібно дізнатися, скільки сторінок у книжки "Swift" — доведеться шукати. А Dictionary дає змогу зберігати дані так: ключ → значення, тобто "Swift" → 120. Тоді пошук зводиться до простого запиту: «дай мені значення за ключем».
Щоб закріпити, порівняймо ідеї в таблиці:
| Колекція | Що зберігає | Як звертаємося | Коли зручно |
|---|---|---|---|
|
елементи підряд | за індексом 0,1,2... | коли важливий порядок, потрібні «перші 10» або перебір за порядком |
|
пари ключ → значення | за ключем (наприклад, рядком) | коли потрібен швидкий доступ за імʼям, ID або кодом |
Невеликий історичний штрих: Dictionary — один із базових типів стандартної бібліотеки Swift. Згодом навколо нього зʼявилися нові зручні операції: злиття, групування тощо. І це не випадково: у більшості застосунків він справді працює як надійна робоча конячка.
Ментальна модель «ключ → значення»
Перед тим як писати код, корисно домовитися про картинку в голові. Dictionary — це як гардероб у театрі: у вас є номерок (ключ), і за ним ви отримуєте пальто (значення). Вам не важливо, у якому порядку висять пальта і скільки людей було перед вами: номерок → пальто, і крапка.
Найважливіша відмінність від масиву тут у тому, що ключ не обовʼязково має бути числом і взагалі не обовʼязково має бути «позицією». Ключем може бути рядок ("Ann"), число (42), булеве значення (true/false) і ще деякі типи. Але логіка завжди одна: за ключем ми хочемо швидко знайти значення.
Можна навіть намалювати це схемою:
flowchart LR
A["Ключ: 'swift'"] --> B["Значення: 120"]
C["Ключ: 'cli'"] --> D["Значення: 30"]
E["Ключ: 'algorithms'"] --> F["Значення: 10"]
У коді це означатиме, що ми створюємо словник, який уміє відповідати на запитання: «скільки сторінок у книжки з такою назвою?».
Тип словника: [Key: Value]
Зараз буде важливий момент про типізацію. Swift — вимоглива мова: вона любить заздалегідь розуміти, що саме ви зберігаєте. Тому тип словника записується так:
[Key: Value]
Тобто це словник, де ключі мають тип Key, а значення — тип Value.
Наприклад, якщо ми хочемо зберігати вік людини за імʼям:
- ключ: імʼя (String)
- значення: вік (Int)
Тоді тип виглядатиме так: [String: Int].
Невеликий приклад, який можна запустити як код верхнього рівня:
import Foundation
var ages: [String: Int] = ["Ann": 24, "Bob": 30]
print(ages) // Порядок може відрізнятися: ["Bob": 30, "Ann": 24]
Зверніть увагу на дві речі. По-перше, ми явно написали тип [String: Int], і тепер словник не дасть випадково записати туди «вік рядком». По-друге, під час print порядок пар може бути не таким, як ви очікували. До цього ми ще повернемося.
Літерал словника: ["a": 1, "b": 2]
Коли ви створюєте масив, ви використовуєте літерал [1, 2, 3]. Зі словником усе схоже, але всередині кожного запису є дві частини: ключ і значення, розділені двокрапкою.
Запис має такий вигляд:
["ключ": значення, "ключ2": значення2]
Ось невеликий приклад каталогу цін:
import Foundation
var prices: [String: Int] = ["coffee": 250, "tea": 180]
print(prices) // Наприклад: ["tea": 180, "coffee": 250]
Важливо звикнути: двокрапка : всередині літерала — це не «тип», а саме роздільник «ключ: значення». Тип ми пишемо зовні як [String: Int].
Порожній словник: [:]
Порожній масив — це []. Логічно було б очікувати, що порожній словник — теж [], але ні: це порожній масив. Для порожнього словника в Swift є спеціальна форма:
[:]
І тут новачків підловлює класична ситуація: якщо написати просто var d = [:], компілятор скаже щось на кшталт «я не розумію, які тут типи ключів і значень». Оскільки [:] не містить жодної пари, вгадувати ні з чого.
Тому порожній словник майже завжди створюють так:
import Foundation
var counters: [String: Int] = [:]
print(counters) // [:]
Альтернативний спосіб — через повну форму типу. Він інколи трапляється в коді й буває корисним, коли ви читаєте чужі проєкти:
import Foundation
var scores = Dictionary<String, Int>()
print(scores) // [:]
Цей варіант довший, але інколи здається наочнішим тим, хто любить, щоб код сам себе пояснював.
var і let: словник можна «заморозити»
Словник — це колекція. А колекції в Swift підкоряються правилам var/let, які ви вже знаєте: якщо колекцію оголошено через let, вона стає незмінною. Це корисно, коли ви хочете захистити дані від випадкових правок, у тому числі й від себе через дві години, коли забудете, що робили.
Порівняймо:
import Foundation
let fixedAges: [String: Int] = ["Ann": 24]
print(fixedAges["Ann"] as Any) // Optional(24)
І змінюваний варіант:
import Foundation
var ages: [String: Int] = [:]
ages["Ann"] = 24
ages["Bob"] = 30
print(ages)
Тут ми використали запис ages["Ann"] = 24. Поки що сприймайте це як «покласти пару ключ → значення». Тонкощі читання, зокрема чому там Optional, ми розберемо в наступній лекції. Тож зараз не лякайтеся виводу Optional(24).
2. Ключі та поведінка словника
Типи ключів і Hashable
Зараз буде момент, де я трохи привідкрию завісу над тим, як це працює в Swift під капотом. Щоб словник міг швидко знаходити значення за ключем, ключ повинен бути спеціального типу: він має підходити для хешування. У Swift це виражається вимогою: ключ має бути Hashable.
Простими словами, ключ має бути таким, щоб Swift умів швидко й стабільно використовувати його як ідентифікатор.
Добра новина: найпотрібніші типи вже Hashable:
- String
- Int
- Bool
Тож ключами спокійно можуть бути рядки й числа.
Приклад: словник «місяць → кількість днів» (поки без календарних тонкощів):
import Foundation
let daysInMonth: [Int: Int] = [1: 31, 2: 28, 3: 31]
print(daysInMonth) // Наприклад: [2: 28, 1: 31, 3: 31]
А ось приклад того, що не може бути ключем (покажу рядком-коментарем, щоб ваш код усе одно компілювався):
import Foundation
// ❌ Масив не може бути ключем, бо він не Hashable
var bad: [[Int]: String] = [:] // error: Type '[Int]' does not conform to protocol 'Hashable'
print("Ключ словника має бути Hashable") // Ключ словника має бути Hashable
І тут важливий практичний висновок: якщо ви намагаєтеся зробити ключем щось складне, компілятор може заборонити це — і найчастіше це на краще. Словник любить прості ключі: рядки, числа, стабільні ідентифікатори.
Ключі унікальні: перезапис значення
Словник відрізняється від масиву ще й тим, що ключі в ньому унікальні. Не можна покласти два значення з одним і тим самим ключем так, щоб вони «жили поруч». Якщо ви записуєте значення за вже наявним ключем, ви замінюєте старе значення.
Це зручно: ми можемо оновлювати запис одним рядком.
import Foundation
var prices: [String: Int] = ["tea": 180]
prices["tea"] = 200
print(prices) // ["tea": 200]
Цю поведінку важливо памʼятати, тому що новачок інколи очікує, що словник зберігає історію. Ні, словник зберігає останнє значення для ключа. Історію ви зберігатимете окремо — зазвичай у масиві, якщо вона взагалі потрібна.
Чому не можна покладатися на порядок
Тут буде одна з найнеприємніших пасток для початківців. Масив за своєю природою впорядкований: елемент 0 іде перед елементом 1. А словник — це структура для пошуку, а не для збереження красивого порядку.
Тому порядок елементів:
- під час print(dictionary)
- під час перебору for (k, v) in dictionary
не варто сприймати як надійну гарантію, на яку можна спиратися в логіці коду.
Словник може друкуватися по-різному залежно від версії, середовища, даних і внутрішніх оптимізацій. Іноді він виглядатиме стабільно, і саме це найгірше: ви звикаєте, а потім одного дня він раптом змінюється.
Невеликий приклад — зверніть увагу на коментар: порядок не гарантований.
import Foundation
let dict: [String: Int] = ["a": 1, "b": 2, "c": 3]
print(dict) // Порядок ключів не є контрактом
3. Приклад: книга → прочитані сторінки
Щоб не залишатися на рівні абстракції, продовжимо наш навчальний консольний мініпроєкт: зберігатимемо, скільки сторінок ви прочитали в кожній книжці. На попередніх заняттях у нас уже були рядки, числа, split, Optional і базові функції — цього достатньо, щоб акуратно побудувати введення.
Зробімо найпростіший парсер рядка формату:
Swift 120
Тобто одне слово-назва і число сторінок.
import Foundation
func parseBookLine(_ line: String) -> (title: String, pages: Int)? {
let parts = line.split(separator: " ")
guard parts.count == 2, let pages = Int(parts[1]) else { return nil }
return (String(parts[0]), pages)
}
Тепер створімо словник і спробуймо додати один запис. Зверніть увагу: ми не займаємося справжнім читанням за ключем — це наступна лекція. Тут наша мета — створити словник і навчитися додавати до нього пари.
import Foundation
var pagesByTitle: [String: Int] = [:]
if let line = readLine(), let book = parseBookLine(line) {
pagesByTitle[book.title] = book.pages
}
print(pagesByTitle) // Наприклад: ["Swift": 120]
Якщо ввести:
Swift 120
то словник отримає пару "Swift" → 120.
Тепер покажемо важливу особливість словника: якщо ви введете книжку з тією самою назвою вдруге, значення заміниться, тому що ключ той самий.
import Foundation
var pagesByTitle: [String: Int] = ["Swift": 120]
pagesByTitle["Swift"] = 150
print(pagesByTitle) // ["Swift": 150]
З погляду нашого мініпроєкту це навіть логічно: ви дочитали ще 30 сторінок, і «поточна кількість» стала 150. Так, поки що ми не використовуємо += і не накопичуємо значення акуратно — це буде трохи пізніше, коли познайомимося зі зручними способами оновлення значень. Поки що просто запамʼятаймо: присвоєння за ключем працює і як «додати», і як «оновити».
Шпаргалка за синтаксисом
Коли ви вперше починаєте писати словники, мозок зазвичай плутає квадратні дужки масиву й квадратні дужки словника. Щоб заспокоїти внутрішнього панікера, корисно тримати одну таблицю перед очима — хоча б кілька днів.
| Що хочемо зробити | Як пишемо |
|---|---|
| Тип словника | |
| Літерал словника | |
| Порожній словник | |
| Додати/оновити пару | |
| Отримати кількість пар | |
І ще: count є і в масиві, і в словнику, але сенс різний. У масиву count — це кількість елементів, а у словника count — кількість унікальних ключів, тобто пар ключ → значення.
4. Типові помилки під час створення Dictionary
Помилка №1: намагатися створити порожній словник як var d = [:] і дивуватися помилці компілятора.
Порожній літерал [:] не містить жодного ключа й жодного значення, тому Swift не може здогадатися про типи. Вирішується просто: або явно задайте тип var d: [String: Int] = [:], або використовуйте Dictionary<String, Int>(). У навчальних завданнях найчастіше обирають перший варіант, тому що він коротший і відразу видно формат [Key: Value].
Помилка №2: очікувати, що словник «памʼятає порядок», і будувати логіку на тому, що «першим надрукується ключ Ann».
Словник призначений для швидкого доступу за ключем, а не для красивого порядку. Іноді порядок виводу здається стабільним, і це створює хибне відчуття безпеки. Але покладатися на це не можна: одного разу ви зміните вхідні дані, версію компілятора або просто запустите все в іншому середовищі — і «першим» стане інший ключ.
Помилка №3: переплутати «додавання другого запису» з «перезаписом першого».
Якщо ви двічі записуєте значення за одним і тим самим ключем, другий раз не «додає ще один запис», а замінює наявний. Це нормально й часто корисно, але якщо ви очікували «історію змін», то словник — не той інструмент: вам потрібен інший формат зберігання, наприклад масив подій.
Помилка №4: обрати невідповідний тип ключа й упертися в Hashable.
Новачки іноді намагаються зробити ключем масив, словник або щось складне без розуміння обмежень. Swift тут доволі суворий: ключ повинен бути Hashable. На старті курсу сприймайте це як правило дорожнього руху: «ключ має бути простим і стабільним». Рядки та числа — найкращий вибір.
Помилка №5: оголосити словник через let, а потім намагатися додавати пари.
Це класика жанру: «я ж просто хочу дописати один рядок». Але let означає незмінність. Якщо словник має зростати або оновлюватися — це var. let залишайте для випадків, коли словник створено й далі використовують лише для читання, наприклад для таблиці констант на кшталт «код → повідомлення».
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ