JavaRush /Курсы /Swift SELF /Изменение массива: append, insert, remove

Изменение массива: append, insert, remove

Swift SELF
11 уровень , 4 лекция
Открыта

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:) при неверном индексе приводит к runtime-ошибке (по сути, программа падает). Это не “плохой 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 и &, которые вы уже видели в теме про 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:) слишком сложным и нечитаемым.
Фигурные скобки легко превратить в “простыню”. Сегодня наша цель другая: одно правило — одна мысль. Если вы видите, что внутри { } у вас три проверки и две вложенные логики, остановитесь и упростите: либо выделите часть в переменную внутри блока, либо временно сделайте правило более прямолинейным. Читаемость сейчас важнее “хитрости”.

1
Задача
Swift SELF, 11 уровень, 4 лекция
Недоступна
Список покупок
Список покупок
1
Задача
Swift SELF, 11 уровень, 4 лекция
Недоступна
Срочная вставка
Срочная вставка
1
Задача
Swift SELF, 11 уровень, 4 лекция
Недоступна
Удаление задачи
Удаление задачи
1
Задача
Swift SELF, 11 уровень, 4 лекция
Недоступна
Чистка заметок
Чистка заметок
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ