Как работать с индексами и итераторами в Swift
Что проверяют
Знаете ли вы, что индекс в Swift не всегда целое число, и понимаете ли, как устроен перебор под капотом. Проверяют обычно через String и срезы массива: на них ломаются ответы, выученные по другим языкам. Следом часто просят объяснить, почему s[3] у строки не компилируется.
Как отвечать
Индекс это ассоциированный тип Index протокола Collection. У Array это Int, но у String, Dictionary и Set индекс непрозрачный, и двигать его можно только методами коллекции:
startIndexиendIndex:endIndexуказывает за последний элемент, обращение по нему падает;index(after:)иindex(_:offsetBy:): сдвиг индекса. ДляStringэто O(n): символы это кластеры графем переменной длины, поэтому доступа по целому числу нет;indices: диапазон допустимых индексов, удобен для перебора с доступом по индексу;firstIndex(of:)иfirstIndex(where:): поиск, возвращает опциональный индекс.
Выход за границы при обращении по индексу это не исключение, а аварийная остановка программы. Поэтому индекс проверяют заранее через indices.contains(i) или берут first и last, которые возвращают опционал.
Отдельно про срезы: ArraySlice сохраняет индексы исходного массива. У array[2...] первый индекс равен 2, и slice[0] упадёт. Правильно брать slice.startIndex или превращать срез в Array.
Итераторы: for-in работает с любой Sequence. Компилятор вызывает makeIterator() и в цикле зовёт next() у IteratorProtocol, пока не придёт nil. Свою последовательность можно сделать, реализовав next(), а lazy откладывает map и filter до реального обхода.
Где спотыкаются
Называют индексы «просто числами» и пытаются взять символ строки как s[3].
Используют enumerated() как источник индексов. Она даёт смещение от нуля, а не индекс коллекции: на срезе или строке это разные вещи, и нужен zip(c.indices, c).
Удаляют элементы по индексам в прямом цикле, и индексы съезжают вплоть до падения. Надёжнее removeAll(where:) или обход в обратном порядке.