1. Зміна масиву
Коли ви лише знайомитеся з масивом, він часто виглядає як «список значень, який ми просто друкуємо». Але щойно ви пишете хоча б трохи реалістичну програму, масив майже відразу перетворюється на живу структуру: користувач додає елементи, видаляє їх, очищає список, виправляє помилки. Масив починає «дихати», і саме тут важливо знати безпечні та читабельні способи його змінювати.
Під «мутацією» (mutation) у Swift ми розуміємо зміну значення змінної. Масив у Swift — це тип-значення (value type), але якщо ви оголошуєте його як var, то можете змінювати його вміст: додавати й видаляти елементи за допомогою методів, які для цього й призначені. Саме їх ми сьогодні й розберемо: append, insert, remove, removeAll, removeAll(where:).
Перше правило мутацій: масив має бути var, а не let
Перш ніж тиснути на педаль append, переконайтеся, що у вашої машини взагалі є двигун. У Swift таким «двигуном» є var. Якщо масив оголошено через let, він стає незмінним за вмістом: ви можете читати елементи, друкувати його, перебирати, але додавати чи видаляти елементи вже не можна. Це не тому, що Swift шкідливий. Це тому, що Swift захищає вас від випадкових змін, коли ви цього не планували.
Ось контрастний приклад:
import Foundation
let fixed = ["A", "B"]
fixed.append("C") // ❌ Помилка компіляції: не можна змінювати масив, оголошений через let
var editable = ["A", "B"]
editable.append("C") // можна додавати елементи
print(editable) // ["A", "B", "C"]
Тут важлива думка така: let — це обіцянка. Ви обіцяєте компілятору — і собі на майбутнє — що склад цього масиву не зміниться. Якщо потім ви раптом намагаєтеся його змінити, компілятор нагадує про цю обіцянку. Іноді досить суворо, але справедливо.
2. Додавання елементів
append(_:): додаємо елемент у кінець масиву
Почнімо з найпривітнішого методу зміни масиву. append(_:) — це як додати пункт у кінець списку. Він не потребує індексу, не змушує думати, що саме зсунеться, і загалом поводиться максимально передбачувано. Якщо ви ведете список завдань, покупок або книжок до прочитання, найчастіше вам потрібен саме він.
Приклад: додаємо нове завдання до списку.
import Foundation
var tasks = ["Купити хліб", "Вигуляти кота"]
tasks.append("Почитати Swift")
print(tasks) // ["Купити хліб", "Вигуляти кота", "Почитати Swift"]
Зверніть увагу: append змінює сам масив. Тому він працює лише з var.
Мініфрагмент для консольного застосунку
Розгляньмо простий застосунок «TaskBox»: у нас є масив завдань, і ми хочемо команду add, яка додаватиме завдання в кінець. Поки без складного розбору — просто покажемо, як виглядає цей фрагмент логіки.
import Foundation
var tasks: [String] = ["Купити хліб"]
let newTask = "Подзвонити мамі"
tasks.append(newTask)
print(tasks) // ["Купити хліб", "Подзвонити мамі"]
У реальній інтерактивній версії значення newTask ми візьмемо з readLine(), але сьогодні нас цікавлять саме мутації масиву.
insert(_:at:): вставлення в позицію і зсув індексів
insert(_:at:) — це вже більш «хірургічний» інструмент. Він вставляє новий елемент за конкретним індексом, а всі елементи праворуч зсуваються на одну позицію. Через це insert часто стає джерелом плутанини: індекси змінюються, і якщо не врахувати цей зсув, легко видалити не той елемент або звернутися не до того індексу.
Приклад: хочемо поставити завдання «ТЕРМІНОВО: зарядити телефон» на початок списку.
import Foundation
var tasks = ["Купити хліб", "Вигуляти кота"]
tasks.insert("ТЕРМІНОВО: зарядити телефон", at: 0)
print(tasks)
// ["ТЕРМІНОВО: зарядити телефон", "Купити хліб", "Вигуляти кота"]
Візуалізація зсуву індексів
Найнаочніше це видно в невеликій таблиці:
| До insert(..., at: 0) | Індекс | Після insert(..., at: 0) |
|---|---|---|
| Купити хліб | 0 | ТЕРМІНОВО: зарядити телефон |
| Вигуляти кота | 1 | Купити хліб |
| — | — | Вигуляти кота |
Тобто старий tasks[0] стає tasks[1], старий tasks[1] стає tasks[2] — і так далі.
Про межі індексу
insert(_:at:) вимагає коректний індекс. Тобто індекс має бути в діапазоні 0...tasks.count.
Тонкий момент: для insert можна вставляти в позицію tasks.count — це означає «вставити в кінець» (за змістом майже як append). А ось індекс, більший за count, — уже помилка.
Приклад із перевіркою:
import Foundation
var tasks = ["A", "B"]
let index = 2 // це рівно tasks.count, вставлення в кінець допустиме
if index >= 0 && index <= tasks.count {
tasks.insert("C", at: index)
}
print(tasks) // ["A", "B", "C"]
3. Видалення за індексом
remove(at:): видаляємо за індексом і отримуємо видалений елемент
Видалення — операція, яка часто потрібна в реальному застосунку: викреслити завдання, видалити неправильне введення, прибрати зайве. У Swift базовий інструмент для цього — remove(at:). Він робить дві речі одночасно: видаляє елемент і повертає видалене значення. Це зручно: можна показати користувачеві, що саме видалили, або використати значення далі в логіці.
Приклад:
import Foundation
var tasks = ["Купити хліб", "Вигуляти кота", "Почитати Swift"]
let removed = tasks.remove(at: 1)
print(removed) // Вигуляти кота
print(tasks) // ["Купити хліб", "Почитати Swift"]
Зверніть увагу, що після видалення елементи праворуч зсуваються вліво, бо масив стає коротшим. Тобто те, що було на індексі 2, може опинитися на індексі 1.
Безпечне видалення: перетворюємо «падіння» на Optional
remove(at:) за некоректного індексу призводить до помилки під час виконання програми. Це не «поганий Swift», це просто ми самі попросили видалити те, чого немає. Тому в реальних сценаріях корисно зробити невелику обгортку: видаляємо лише тоді, коли індекс коректний, інакше повертаємо nil.
import Foundation
func safeRemove(at index: Int, from array: inout [String]) -> String? {
if index >= 0 && index < array.count {
return array.remove(at: index)
} else {
return nil
}
}
var tasks = ["A", "B", "C"]
if let removed = safeRemove(at: 10, from: &tasks) {
print("Видалено: \(removed)")
} else {
print("Видаляти нічого: індекс поза межами") // повідомлення для користувача
}
Тут ми використовуємо inout і &, які ви вже бачили раніше: це звичний спосіб дати функції дозвіл змінювати цей масив.
4. Повне очищення та очищення за правилом
removeAll(): видалити все
Іноді користувач не хоче видаляти елементи по одному — він хоче почати спочатку. Для цього є removeAll(): масив стає порожнім, count стає 0, а isEmpty стає true. Це схоже на «Очистити кошик» або «Скинути список».
Приклад:
import Foundation
var tasks = ["A", "B", "C"]
tasks.removeAll()
print(tasks) // []
print(tasks.count) // 0
Це проста операція за змістом, але важливо пам’ятати, що після неї будь-яка спроба звернутися до tasks[0] буде помилкою, бо масив порожній.
removeAll(where:): видаляємо за правилом
Ось ми й дійшли до найцікавішого методу лекції. removeAll(where:) — це коли ви говорите масиву: «Видали всі елементи, які відповідають умові». І тут з’являється нова форма запису: фігурні дужки { }. Поки не сприймайте це як щось складне. Сьогодні це просто вбудоване правило, яке отримує елемент і повертає true або false.
Цей метод додали до стандартної бібліотеки, бо операція «видалити все, що відповідає умові» трапляється дуже часто, а писати її вручну легко з помилками й не надто читабельно. У Swift це оформлено як removeAll(where:).
Як читати removeAll(where:) словами
Видалити всі елементи, для яких правило повертає true.
Це ключова фраза. Якщо ви запам’ятаєте лише її, ви вже уникнете типової помилки «я написав умову навпаки».
Мінімальна модель правила
У спрощеному вигляді сигнатура така:
removeAll(where: { element in Правило типу Bool })
Тобто всередині { } у нас правило: воно на вході отримує element, а на виході повертає Bool.
Приклад: видалимо всі відʼємні числа.
import Foundation
var values = [3, -1, 4, -2, 0]
values.removeAll(where: { element in
element < 0 // правило
})
print(values) // [3, 4, 0]
Тут правило: «Елемент менший за нуль». Якщо це true — елемент видаляється.
Приклад на рядках
Уявімо, що до списку завдань випадково потрапили порожні рядки — наприклад, коли користувач натиснув Enter і нічого не ввів. Ми хочемо видалити такі порожні значення.
import Foundation
var tasks = ["Купити хліб", "", "Вигуляти кота", ""]
tasks.removeAll(where: { task in
task.isEmpty // правило
})
print(tasks) // ["Купити хліб", "Вигуляти кота"]
Ми використовуємо task.isEmpty — це проста перевірка, і вона чудово читається.
Чому параметр називається where:
Параметр називається where: (за змістом: «де умова така-то»), а правило, яке повертає Bool, часто називають предикатом. Не обов’язково запам’ятовувати слово «предикат» як термін для іспиту, але корисно розуміти зміст: це функція або правило, яке для кожного елемента дає відповідь «так» чи «ні».
5. Корисні нюанси
Схема: команда перетворюється на зміну масиву
Щоб пов’язати все в один застосунок і не сприймати методи як розрізнені заклинання, корисно бачити загальний процес: ми отримуємо команду, вирішуємо, що робити, змінюємо масив і друкуємо результат.
flowchart TD
A["readLine()"] --> B[Розбираємо команду]
B --> C{Яка команда?}
C -->|add| D["tasks.append(...)"]
C -->|insert| E["tasks.insert(..., at: ...)"]
C -->|remove| F["tasks.remove(at: ...)"]
C -->|clear| G["tasks.removeAll()"]
C -->|clean| H["tasks.removeAll(where: ...)"]
D --> I["print(tasks)"]
E --> I
F --> I
G --> I
H --> I
Ця схема навмисно проста: ми не ускладнюємо її майбутніми темами. Але вона допомагає тримати в голові, що методи масиву — це лише «двигуни», які спрацьовують після обробки введення користувача.
Нюанс: чому remove(at:), а не «removeAtIndex»
Якщо ви колись бачили старі приклади коду або обговорення, могли натрапити на більш розлогі назви методів. Згодом Swift привів API до більш читабельного стилю, щоб виклики читалися як фраза. Наприклад, remove(at:) — це ніби «видалити на позиції …», а removeAll(keepingCapacity:) — «видалити все, зберігши місткість». Такі перейменування та уніфікацію стилю описували в ініціативі щодо API Guidelines для стандартної бібліотеки.
У межах цієї лекції вам достатньо звикнути до таких назв: append, insert(_:at:), remove(at:), removeAll(), removeAll(where:) — це сучасні, читабельні й доречні імена, і саме їх ви бачитимете в актуальному Swift-коді.
6. Типові помилки під час мутацій масиву
Помилка № 1: намагатися викликати append/insert/remove на масиві, оголошеному через let.
Це класика: «я ж просто додам один елемент…». Але let означає, що склад масиву незмінний. Swift не дасть вам цього зробити, і це добре: ви ловите проблему на етапі компіляції, а не вже під час роботи застосунку. Рішення просте: якщо список має змінюватися, оголошуйте його через var.
Помилка № 2: некоректні індекси в insert(_:at:) і remove(at:).
Найчастіше плутають count і останній індекс. У масиву з 3 елементів count == 3, а останній індекс — 2. Для remove(at:) допустимі індекси 0..<count. Для insert(_:at:) діапазон трохи ширший: 0...count — можна вставити в кінець. Звичка перевіряти межі перед операцією економить багато нервів.
Помилка № 3: забути, що після insert і remove індекси зсуваються.
Вставлення зсуває елементи праворуч, видалення — вліво. Тому якщо ви зберігаєте якийсь індекс, а потім змінюєте масив, цей індекс може почати вказувати вже на інший елемент. Найкращі ліки — спочатку продумати порядок дій і частіше спиратися на поточні значення, а не на старі індекси з минулого.
Помилка № 4: переплутати зміст умови в removeAll(where:).
removeAll(where:) видаляє елементи, для яких умова повертає true. Дуже поширена помилка: написати умову «що залишити», як у filter, і отримати протилежний результат. Перед тим як писати правило, корисно проговорити його словами: «видалити всі елементи, які …».
Помилка № 5: зробити правило для removeAll(where:) занадто складним і нечитабельним.
Фігурні дужки легко перетворити на довгий, заплутаний блок. Сьогодні наша мета інша: одна умова — одна думка. Якщо ви бачите, що всередині { } у вас три перевірки і дві вкладені умови, зупиніться й спростіть: або винесіть частину в змінну всередині блоку, або тимчасово зробіть правило прямолінійнішим. Читабельність зараз важливіша за хитрощі.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ