В чем разница между перебором массива и строки в Swift?

Ответ

Хотя и Array, и String соответствуют протоколу Sequence, их внутреннее устройство и семантика итерации различаются.

Перебор массива (Array)

Массив — это коллекция элементов с целочисленной индексацией (O(1) доступ).

let numbers: [Int] = [10, 20, 30]

// 1. Цикл for-in
for number in numbers {
    print(number) // 10, 20, 30
}

// 2. Прямой доступ по индексу
for i in 0..<numbers.count {
    print(numbers[i]) // 10, 20, 30
}

Перебор строки (String)

Строка — это коллекция расширенных графемных кластеров (Character). Доступ по индексу не является O(1) из-за Unicode.

let greeting = "Hello🇺🇦"

// 1. Цикл for-in (перебор Character)
for character in greeting {
    print(character) // H, e, l, l, o, 🇺🇦 (флаг как один Character)
}

// 2. Использование индексов String.Index
for index in greeting.indices {
    print(greeting[index]) // Аналогичный вывод
}

// НЕВЕРНО: print(greeting[0]) - Ошибка компиляции

Сравнительная таблица

Аспект Массив (Array<T>) Строка (String)
Тип элемента Любой тип T Character (графемный кластер)
Индексация Int (быстрая, O(1)) String.Index (относительная позиция)
Изменяемость Элементы можно менять по индексу Строка — value type, изменение создает копию
Сложность итерации O(n) O(n), но с большей константой из-за Unicode

Почему так? Строка должна корректно обрабатывать составные символы (например, "é" = e + ́ ) и эмодзи (например, "👨‍👩‍👧‍👦"), поэтому прямой доступ по целочисленному индексу невозможен.

Ответ 18+ 🔞

Да ты посмотри, какая хуйня творится! Вроде и Array, и String — оба под протокол Sequence подписались, типа «мы коллекции, перебирайте нас». А на деле, сука, внутри у них всё устроено по-разному, как будто это не родные братья, а соседские дети, которые только в одной песочнице играют.

Ковыряемся в массиве (Array)

Массив — он простой, как три копейки. Коллекция с индексами, как в очереди за колбасой: первый, второй, третий. Доступ к любому элементу — мгновенный, O(1), то есть не надо мозги ебать.

let numbers: [Int] = [10, 20, 30]

// 1. Просто тупо перебираем
for number in numbers {
    print(number) // 10, 20, 30
}

// 2. Можно и по индексам пройтись, если очень хочется
for i in 0..<numbers.count {
    print(numbers[i]) // 10, 20, 30
}

Тут всё предсказуемо, как день зарплаты — пришёл и взял.

А вот строка (String) — это уже ёперный театр

Строка — это не просто буковки. Это коллекция каких-то расширенных графемных кластеров, или, проще говоря, Character. И доступ по индексу тут не O(1), потому что Unicode, блядь, со своими приколами.

let greeting = "Hello🇺🇦"

// 1. Цикл for-in — перебираем Character'ы
for character in greeting {
    print(character) // H, e, l, l, o, 🇺🇦 (и флаг — это один Character, ёпта!)
}

// 2. Через индексы String.Index (специальные, блядь, не Int!)
for index in greeting.indices {
    print(greeting[index]) // То же самое, но с мозгоёбством
}

// НЕЛЬЗЯ просто взять и сделать greeting[0] — компилятор тебя пошлёт нахуй с ошибкой.

Представь, пытаешься найти человека в толпе не по номеру в списке, а по его «относительной позиции в пространстве». Вот это и есть String.Index.

Сравнительная таблица, чтобы совсем пиздец

Аспект Массив (Array<T>) Строка (String)
Что внутри Любой тип T (хоть Int, хоть твои носки) Character (это графемный кластер, ёбта!)
Как индексируется Int (быстро, O(1)) String.Index (относительная позиция, не O(1))
Можно менять? Да, элементы меняй по индексу как хочешь Строка — value type, любое изменение создаёт новую копию
Сложность перебора O(n) O(n), но с такой константой, что овердохуища

А почему так, спрашиваешь? Да потому что строка должна правильно работать с составными символами (типа "é", который из e и ́ склеен) и эмодзи (вот этот семейный "👨‍👩‍👧‍👦"). Поэтому прямого доступа по целочисленному индексу тут нет и быть не может, иначе получится полная хуйня с отображением.