1. Чому діапазони — це зручно
Коли ми пишемо програму, майже завжди працюємо з обмеженнями: вік має бути від 0 до 120, оцінка — від 0 до 100, час доби — від 0 до 23, пароль — щонайменше 8 символів, а кількість спроб — не більше трьох. У коді такі обмеження часто перетворюються на довгі умови й числа, що повторюються.
Проблема в тому, що межі легко переплутати. Можна забути, чи входить 18 до «дорослого» віку, можна помилитися на одиницю — привіт, класична off-by-one — або розʼїхатися в різних місцях програми: в одному місці ви перевірили <= 100, а в іншому випадково < 100. Діапазони в Swift — це спосіб компактно й однаково всюди виразити «правило меж».
2. Діапазон як об’єкт: ... і ..<
Якщо говорити просто, діапазон — це «відрізок» чисел між двома межами. Swift дозволяє записувати діапазони так, як ви б сказали вголос: «від 1 до 5 включно» або «від 0 до 10, але 10 не включаємо». Звідси й дві головні форми: ... і ..<.
Закритий діапазон: a...b
Закритий діапазон включає обидві межі: і a, і b.
let levels = 1...5
print(levels.contains(1)) // true
print(levels.contains(5)) // true
print(levels.contains(6)) // false
Тут 1 входить, 5 входить, 6 — уже ні. Це просто шкільна математика.
Напіввідкритий діапазон: a..<b
Напіввідкритий діапазон включає ліву межу, але не включає праву.
let digits = 0..<10
print(digits.contains(0)) // true
print(digits.contains(9)) // true
print(digits.contains(10)) // false
Це надзвичайно зручно для «кількісних» речей. Наприклад, якщо у вас n елементів, то індекси часто йдуть від 0 до n-1, тобто 0..<n.
Навіть якщо ви ще не проходили масиви, сама ідея «n значень» трапляється постійно: «повтори n разів», «зроби спроби від 0 до n-1», «пройди години від 0 до 23».
Міні-таблиця: як читати ... і ..<
| Запис | Як читається вголос | Чи включає праву межу? |
|---|---|---|
|
«від a до b включно» | Так |
|
«від a до b, але b не включаємо» | Ні |
Щоб закріпити різницю, корисно подумки перевіряти «крайні значення»: підставте b і запитайте себе — воно має входити чи ні?
3. Діапазон як правило: зберігаємо в let і використовуємо повторно
До цього моменту ви, найімовірніше, сприймали діапазон як «річ для for». Але насправді діапазон — це окреме значення, яке можна зберегти у змінну або константу й застосовувати багато разів. Це схоже на ситуацію, коли ми зберігаємо число в let maxAttempts = 3 замість того, щоб писати 3 у десяти місцях.
Уявімо, що ми пишемо невеликий консольний застосунок «Вхід до клубу».
let adultAgeRange = 18...120
let age = Int(readLine() ?? "") ?? 0
if adultAgeRange.contains(age) {
print("Ласкаво просимо! Вам \(age) років.")
} else {
print("Вибачте, вхід дозволено лише з 18 років.")
}
Тут важлива ідея: числа 18 і 120 тепер живуть в одному місці — у діапазоні. Якщо правила зміняться, ви змінюєте діапазон, а не переписуєте умови по всьому коду.
Такий підхід особливо корисний, коли ви перевіряєте введення користувача. У користувача є талант вводити дивні значення: -10, 999, порожній рядок, «двадцять». Ми не можемо заборонити цього, але можемо акуратно перевірити.
4. Діапазони в for-in: межі циклу без сюрпризів
Ви вже вмієте писати цикли, а діапазони — основний інструмент для for-in, коли ви перебираєте числа. Сьогодні ми просто робимо цей механізм усвідомленим: ви обиратимете ... або ..< не навмання, а тому, що так правильно за змістом.
«Зробити n разів»: майже завжди 0..<n
Якщо вам потрібно виконати дію рівно n разів, зручно йти від 0 до n-1.
let n = 5
for i in 0..<n {
print("Ітерація #\(i)")
}
// Ітерація #0
// Ітерація #1
// Ітерація #2
// Ітерація #3
// Ітерація #4
Чому це так популярно? Тому що «кількість» — це n, а індексування, та й узагалі рахунок «з нуля», у програмуванні трапляється постійно.
«Від 1 до n включно»: зручно 1...n
Якщо у вас «людський» рахунок: перший, другий, третій… і ви хочете включити n, беріть закритий діапазон.
let n = 5
for step in 1...n {
print("Крок \(step)")
}
// Крок 1
// Крок 2
// Крок 3
// Крок 4
// Крок 5
Блок-схема вибору діапазону
flowchart TD
A["Потрібно повторити рівно n разів?"] -->|Так| B["0..<n"]
A -->|Ні| C["Потрібні людські числа від 1 до n?"]
C -->|Так| D["1...n"]
C -->|Ні| E["Подумайте: права межа має входити чи ні?"]
Сенс простий: спочатку визначаємо задачу, а потім обираємо синтаксис. Не навпаки.
Порожній діапазон і граничні випадки
У програмуванні часто здається, що якщо межі однакові, то й значення буде одне. Для закритого діапазону це правда: 5...5 містить рівно одне число — 5.
А от напіввідкритий діапазон 5..<5 не містить жодного значення, тобто є порожнім. Це логічно: ми включаємо ліву межу, але виключаємо праву, а вони однакові — отже, не лишається жодного значення.
Це корисно, тому що порожній діапазон чудово поводиться в циклі: він просто не виконується жодного разу.
for i in 5..<5 {
print(i) // цей код не виконається жодного разу
}
print("Готово") // Готово
Практичний зміст такий: якщо «кількість» виявилася нульовою, 0..<0 — це чесне «виконати 0 разів». Без спеціальних if.
5. stride(from:to:by:): коли крок не дорівнює 1
Цикли по діапазону йдуть із кроком 1. Але іноді нам потрібно «стрибати» по числах: лише парні, лише кожне третє, зворотний відлік, «покажи 10, 8, 6, 4, 2, 0»… Ось тут і з’являється stride.
Ідея stride: «іди від start до end, роблячи крок by»
У Swift є функція stride(from:to:by:), яка створює послідовність чисел із заданим кроком. Важливо розуміти семантику: варіант із to: дає значення на напіввідкритому інтервалі [start, end), тобто не доходить до end.
Наприклад, виведімо парні числа, менші за 10.
for i in stride(from: 0, to: 10, by: 2) {
print(i)
}
// 0
// 2
// 4
// 6
// 8
Якщо вам потрібно «до 10 включно», у stride є сусід — stride(from:through:by:). Він працює як [start, end] — тобто права межа потенційно включається, — але з важливою умовою: кінець потрапить лише тоді, коли його можна досягти кроком.
Від’ємний крок: ідемо вниз
stride уміє рухатися назад — просто задаємо від’ємний by:.
for t in stride(from: 10, to: 0, by: -2) {
print(t)
}
// 10
// 8
// 6
// 4
// 2
Зверніть увагу: to: 0 не включається, тому 0 не надрукується. Якщо вам принципово вивести і 0, використовуйте through: 0.
Дуже важливе правило: by: не має бути нулем
Крок by: 0 — це логічна катастрофа: ви нікуди не рухаєтеся, а цикл потенційно стає нескінченним. Тому крок має бути ненульовим і скінченним.
Для новачка це добра підказка: якщо ви задаєте «крок», переконайтеся, що це справді крок, а не стояння на місці.
Обережно з дробовими кроками
На цілих числах stride майже завжди поводиться передбачувано. А от із дробовими значеннями (Double) може з’являтися накопичення похибок через особливості арифметики з плаваючою крапкою: ви можете очікувати 1.0, 1.1, 1.2 тощо, а отримати значення, що відрізняються на мікроскопічну похибку.
Ми зараз не заглиблюємося в допуски та порівняння Double; просто запам’ятайте як дорожній знак: з дробами треба бути уважнішими.
6. Де застосовувати: мінізастосунок «Термінал кінотеатру»
Зараз ми зберемо невеликий консольний сценарій: користувач вводить вік і час сеансу, а програма перевіряє, чи можна продати квиток. Паралельно ми покажемо, як stride допомагає виводити «кожне друге місце» — наприклад, щоб розсаджувати людей через одне місце.
Ми не використовуємо масиви, функції та switch — лише те, що у вас уже є, плюс діапазони та stride.
Діапазони як правила: вік і години
Спочатку заведемо правила:
- допустимий вік: 0...120
- дорослий: 18...120
- час сеансу: 0..<24 (години доби)
let validAge = 0...120
let adultAge = 18...120
let validHour = 0..<24
print("Введіть вік:")
let age = Int(readLine() ?? "") ?? -1
print("Введіть годину сеансу (0-23):")
let hour = Int(readLine() ?? "") ?? -1
if !validAge.contains(age) || !validHour.contains(hour) {
print("Помилка введення: перевірте вік і час сеансу.")
} else {
if adultAge.contains(age) {
print("Квиток можна продати. Вам \(age) років, сеанс о \(hour):00.")
} else {
print("Ви неповнолітні. Перевірте обмеження фільму.")
}
}
Зверніть увагу, як читається виклик validHour.contains(hour). Це майже як звичайна українська фраза: «валідний діапазон годин містить введену годину».
І, на відміну від перевірки hour >= 0 && hour < 24, ви не ризикуєте переплутати, де <=, а де <.
stride для місць «через одне»
Припустімо, у нас зал із місцями від 1 до 20, і ми хочемо запропонувати користувачеві місця через одне: 1, 3, 5, ... Це виглядає як ідеальна робота для stride.
print("Доступні місця (через одне):")
for seat in stride(from: 1, to: 21, by: 2) {
print("Місце #\(seat)")
}
Чому to: 21, а не to: 20? Бо to: не включає праву межу. Ми хочемо дійти до 19, а для цього зручно поставити верхню межу на 21: так 20 точно не потрапить, а 19 потрапить.
Невеликий сценарій у поєднанні
Тепер поєднаємо все в один короткий сценарій: якщо введення коректне — покажемо доступні місця.
let validAge = 0...120
let validHour = 0..<24
print("Вік:")
let age = Int(readLine() ?? "") ?? -1
print("Година сеансу (0-23):")
let hour = Int(readLine() ?? "") ?? -1
if validAge.contains(age) && validHour.contains(hour) {
print("Гаразд, сеанс о \(hour):00. Можливі місця:")
for seat in stride(from: 1, to: 21, by: 2) {
print(seat, terminator: " ")
}
print() // перенесення рядка
} else {
print("Некоректні дані. Спробуйте ще раз.")
}
Тут ми ще й застосували досвід із форматуванням виводу: друкуємо місця в один рядок через terminator: " ", а потім виконуємо print() для перенесення рядка.
7. Типові помилки: діапазони та stride
Помилка №1: переплутати ... і ..< на правій межі.
Найчастіша пастка — коли ви думаєте «до 10», пишете 0...10, а потім дивуєтеся, що вийшло 11 значень. Або навпаки: вам потрібно включити 23 (години доби), а ви пишете 0..<23 і тихо втрачаєте останню годину. Лікується звичкою перевіряти крайні значення: «чи входить права межа, чи ні?».
Помилка №2: намагатися робити «n разів» через 0...n.
Діапазон 0...n містить n + 1 значень, і в циклі ви отримаєте на одну ітерацію більше. Це майже завжди випадкова помилка. Для «n разів» найчастіше підходить 0..<n, бо він дає рівно n кроків.
Помилка №3: очікувати, що 5..<5 дасть одне значення.
Напіввідкритий діапазон за однакових меж порожній. Це не баг, а корисна логіка: «нуль значень» чесно перетворюється на «нуль ітерацій». Якщо вам потрібне одне значення, використовуйте 5...5.
Помилка №4: задати stride(..., by: 0) або крок не в той бік.
Крок має бути ненульовим.
І ще: якщо ви йдете вниз (from: 10 до to: 0), крок має бути від’ємним, інакше ви взагалі не наближатиметеся до межі. У найкращому разі цикл дасть порожній результат, у найгіршому — логіка виявиться зламаною, а ви довго шукатимете, чому нічого не друкується.
Помилка №5: чекати від stride(from:to:by:), що він включить праву межу.
stride(from:to:by:) працює як напіввідкритий інтервал [start, end), тобто до end він не доходить.
Якщо вам потрібно включати праву межу, ви або використовуєте through: і перевіряєте досяжність, або акуратно підбираєте to: як «на одиницю далі» для цілих чисел.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ