1. for-in і ітерації
Якщо ви колись ловили себе на думці: «Мені треба попросити користувача ввести число 10 разів… а я зараз просто 10 разів напишу readLine()», — вітаю: ви вже майже винайшли цикл, тільки у формі страждання. Цикл — це спосіб сказати програмі: «Зроби ось цей шматок коду кілька разів». Причому «кілька» може означати як строго фіксовану кількість повторів, так і повторення доти, доки умова істинна. Але це — тема наступних лекцій.
У цій лекції ми розглядаємо найпростіший випадок: коли кількість повторень відома наперед або легко задається числом n. Наприклад, прочитати n значень, вивести числа від 1 до n, обчислити суму перших n чисел, вивести n рядків звіту. Саме такі задачі й належать до типових для for-in.
Коли вперше бачите for i in ... { ... }, це легко сприйняти як заклинання. Насправді все набагато простіше. У Swift цикл for-in означає: «для кожного значення з набору виконай тіло циклу». Найчастіше в перших задачах таким «набором» стає діапазон цілих чисел — наприклад, 1...5 або 0..<n.
Загальний шаблон виглядає так:
for змінна in набірЗначень {
// тіло циклу
}
змінна — це тимчасова назва, яка по черзі прийматиме значення з набору. Для кожного такого значення виконується тіло циклу.
Перша практика: друкуємо числа й учимося бачити ітерації
Почнімо з максимально простого: виведемо числа від 1 до 5. Це корисно не тому, що нам життєво потрібне число 5, а тому, що так легше побачити, скільки разів виконається тіло циклу.
for i in 1...5 {
print("i = \(i)")
}
Тут тіло циклу виконається 5 разів. Змінна i по черзі буде дорівнювати 1, 2, 3, 4, 5.
Дуже важлива думка: одне виконання тіла циклу називається ітерацією. У цьому прикладі ітерацій рівно 5.
2. Діапазони ... і ..<: включно чи без правої межі
Тут ми підходимо до місця, де зазвичай і виникає класична помилка «на один раз більше або менше». У Swift є два базові оператори діапазону, і вони різняться правою межею.
- Діапазон a...b читається як: «від a до b включно».
- Діапазон a..<b читається як: «від a до b, але b не включаємо».
Щоб це легше запамʼятати, можна уявити, що в ..< права межа — наче скляні двері: ви її бачите, але перейти за неї не можете.
Порівняймо в таблиці:
| Діапазон | Які значення дає | Скільки ітерацій (якщо крок 1) |
|---|---|---|
|
1, 2, 3, 4, 5 | 5 |
|
1, 2, 3, 4 | 4 |
|
0, 1, 2, 3, 4 | 5 |
Зверніть увагу на останній рядок: 0..<5 дає 5 значень. Саме тому діапазон 0..<n так часто використовують.
3. «Рівно n разів»: 0..<n і «по-людськи»: 1...n
Уявіть, що у вас є число n, і ви хочете повторити дію рівно n разів. Не «від 1 до n» як математичну послідовність, а саме «зробити n повторів».
Ось тут і стає у пригоді дуже зручний стиль:
- 0..<n дає значення 0, 1, 2, ..., n-1
- і таких значень рівно n
Тобто діапазон 0..<n можна майже дослівно читати як «рівно n разів».
let n = 3
for i in 0..<n {
print("step \(i)")
}
// крок 0
// крок 1
// крок 2
Тут тіло циклу виконалося 3 рази — саме так, як нам і потрібно.
Коли зручніше 1...n
1...n добре підходить тоді, коли вам важлива саме нумерація від 1, а не «рівно n повторів». Наприклад, якщо ви виводите «День 1», «День 2», «День 3», людині значно природніше бачити дні, що починаються з 1, а не з 0.
let n = 3
for day in 1...n {
print("День \(day)")
}
// День 1
// День 2
// День 3
Отже, у нас є два типові стилі:
- 0..<n — «рівно n разів»
- 1...n — «від 1 до n, по-людськи»
4. Область видимості та _: що живе всередині циклу, а що — ні
Новачки часто хочуть зробити так: «А я після циклу виведу i». І тут компілятор Swift ввічливо, але твердо, відповідає: «Не можу, такої змінної вже не існує».
Чому? Тому що змінну циклу оголошують всередині конструкції for, а живе вона лише всередині тіла циклу.
for i in 1...3 {
print(i)
}
print(i) // так не можна: i поза областю видимості
print("Готово")
Якщо вам потрібне значення ззовні, зазвичай заводять окрему змінну до циклу і оновлюють її всередині. Але у for-in це найчастіше й не потрібно.
Якщо індекс узагалі не потрібен: _
Іноді вам не потрібен номер ітерації, а потрібен лише сам факт повторення. Наприклад, вивести "tick" 3 рази. Тоді писати for i in ... і не використовувати i — це трохи незручно. Swift дає для цього спеціальний символ _, який означає: «значення є, але воно мене не цікавить».
for _ in 0..<3 {
print("tick")
}
// tick
// tick
// tick
Це читається майже як «зроби тричі».
5. Лічильник і акумулятор: дві головні ролі змінних у циклах
Коли ви працюєте з циклами, найчастіше або рахуєте події, або підсумовуєте значення.
- Лічильник — змінна, яка збільшується, коли трапляється подія.
- Акумулятор — змінна, яка накопичує результат, наприклад суму.
Ключовий момент: такі змінні зазвичай оголошують до циклу, змінюють усередині, а використовують після. Якщо оголосити акумулятор усередині циклу, він щоразу створюватиметься заново. Тобто ви ніби накопичуєте гроші, але щоразу купуєте нову скарбничку.
Приклад: сума від 1 до n як акумулятор
let n = 5
var sum = 0
for i in 1...n {
sum = sum + i
}
print("sum = \(sum)") // sum = 15
Мінісхема for-in: без магії та «під капотом»
Корисно один раз уявити цикл як просту послідовність кроків: не як «оператор», а як процес.
Псевдокод / схема:
Беремо діапазон (наприклад, 1...3)
Беремо перше значення (1) → i = 1 → виконуємо тіло
Беремо наступне (2) → i = 2 → виконуємо тіло
Беремо наступне (3) → i = 3 → виконуємо тіло
Значення закінчилися → виходимо з циклу
Тобто for-in — це не телепорт і не паралельне виконання, а просто акуратне повторення.
6. Мініпроєкт «Копілка»: читаємо n чисел і підсумовуємо
Тепер зробімо приклад, який виглядає як невеликий реальний застосунок. Ми почнемо створювати консольний застосунок «Копілка»: користувач вводить, скільки днів він відкладає гроші, а потім уводить суму за кожен день. Програма рахує загальну суму й виводить підсумок. На наступних лекціях ми розвинемо цю саму ідею — наприклад, читати до команди "stop", пропускати неправильні значення тощо. Але зараз нам потрібні саме for-in і діапазони.
Спочатку прочитаємо days. Тут усе знайоме: readLine() → рядок → Int(...) → значення за замовчуванням через ??.
print("Скільки днів ви відкладали гроші?")
let daysText = readLine() ?? "0"
let days = Int(daysText) ?? 0
print("Днів: \(days)")
Поки що ми лише зчитали число. Тепер — головне: повторити введення days разів. Ось тут for day in 1...days просто напрошується в код, бо «день 1», «день 2» — це людський спосіб лічби.
print("Скільки днів ви відкладали гроші?")
let days = Int(readLine() ?? "0") ?? 0
var total = 0
for day in 1...days {
print("Введіть суму за день \(day):")
let money = Int(readLine() ?? "0") ?? 0
total = total + money
}
print("Усього у скарбничці: \(total)")
Тут важливо помітити одразу кілька речей.
По-перше, total оголошено до циклу, інакше ми б «обнуляли скарбничку» щодня.
По-друге, діапазон 1...days включає days, тому якщо days = 3, цикл виконається для day = 1, 2, 3 — рівно 3 рази.
По-третє, якщо days виявиться 0, то 1...0 — поганий варіант. На практиці такий цикл просто не виконається. Проте новачкові важливіше інше: треба вирішити, що робити, якщо days <= 0. Зазвичай це вирішують через if — ви це вже вмієте.
Додаймо невеликий захист: якщо днів 0 або менше, ми одразу повідомляємо про це і не просимо вводити суми.
let days = Int(readLine() ?? "0") ?? 0
if days <= 0 {
print("Потрібно ввести додатне число днів.")
} else {
var total = 0
for day in 1...days {
let money = Int(readLine() ?? "0") ?? 0
total = total + money
}
print("Усього: \(total)")
}
Цей приклад ще не ідеальний, адже ми поки що не розрізняємо «користувач увів 0» і «користувач увів нісенітницю». Але на цьому етапі він чудово показує механіку for-in по діапазону та роботу акумулятора.
Варіант із 0..<days: коли «номер спроби» зручніший за «день»
Іноді вам потрібне саме «повторити days разів», а всередині циклу вивести, наприклад, «ввід №1, №2, №3». Тоді 0..<days теж підходить, але доведеться акуратно перетворити 0, 1, 2 на «1, 2, 3» для виводу людині.
let days = Int(readLine() ?? "0") ?? 0
var total = 0
for i in 0..<days {
print("Введення №\(i + 1):")
let money = Int(readLine() ?? "0") ?? 0
total = total + money
}
print("Усього: \(total)")
Зверніть увагу на i + 1. Це часта й цілком нормальна річ: програма рахує з нуля, а користувач — з одиниці. І це не «погано» і не «добре», а просто різні зручності для різних задач.
7. Типові помилки під час роботи з for-in і діапазонами
Помилка №1: переплутати ... і ..< та отримати «на один раз більше або менше».
Це найпопулярніша історія у світі циклів. Ви хотіли три рази, написали 0...3 і отримали чотири ітерації: 0, 1, 2, 3. Це легко виправити простим правилом: якщо вам потрібно «рівно n разів», майже завжди беріть 0..<n. А якщо хочете «від 1 до n, як у підручнику», беріть 1...n.
Помилка №2: використовувати 0...n замість 0..<n і щиро не розуміти, чому раптом n+1.
Ця помилка підступна тим, що виглядає логічно: «Ну я ж хочу від 0 до n». Проблема в тому, що це справді «від 0 до n включно», тобто n+1 значення. Корисно проговорювати діапазон словами: «Права межа включається?» — якщо так, то ітерацій буде більше.
Помилка №3: оголосити акумулятор усередині циклу й щоразу його скидати.
Якщо написати var total = 0 всередині тіла циклу, то на кожній ітерації ви створюєте нову змінну total, яка починається з нуля, і в підсумку «накопичення» не відбувається. Це схоже на спробу накопичити гроші, але щовечора викидати скарбничку у вікно. Акумулятори й лічильники майже завжди оголошують до циклу.
Помилка №4: намагатися використовувати змінну циклу (i, day) після завершення циклу.
Змінна циклу живе лише всередині блоку { ... }. Якщо вам потрібне «останнє значення» або якась інформація після циклу, заведіть окрему змінну до циклу й оновлюйте її всередині. Але частіше правильніше сформулювати задачу інакше: зазвичай після циклу вам потрібен результат, наприклад total, а не саме i.
Помилка №5: обирати «рахунок від 0» там, де ви виводите значення для людини, і отримувати дивний вивід.
Показувати користувачеві «День 0» або «Ввід №0» — технічно не помилка компіляції, але UX виходить дивний. Якщо значення потрапляє в повідомлення для людини, частіше зручно або ітеруватися 1...n, або додавати 1 під час виводу (i + 1). Головне — робити це свідомо, а не випадково.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ