В этом обобщённом коде определите, для какого значения вычисление count может потребовать обхода всех элементов и почему.
let array = Array(0..<10_000)
let text = String(repeating: "🙂", count: 10_000)
print(array.count)
print(text.count)
array.count у Array обычно вычисляется за O(1), а text.count у String может потребовать обхода всей строки и иметь сложность O(n). Причина в том, что Collection не обязуется обеспечивать постоянное время для count: оно зависит от стоимости вычисления расстояния между индексами.
Протокол Collection объединяет структуры с разными способами доступа к элементам: массивы, строки, словари и пользовательские коллекции. Поэтому его API опирается не на целочисленные позиции, а на абстрактные индексы и операции над ними.
Такой дизайн позволяет корректно работать со структурами вроде String, где один видимый символ может состоять из нескольких кодовых единиц. Универсальный алгоритм не должен предполагать, что переход между индексами или подсчёт элементов всегда равносилен арифметике над целыми числами.
Если обобщённая функция вызывает count, разработчик может ошибочно считать эту операцию дешёвой. Для большого текста или пользовательской коллекции это способно привести к полному обходу элементов внутри горячего участка кода.
В приведённом примере оба значения равны 10_000, но стоимость их получения различается. У Array размер хранится как часть структуры, тогда как для String нужно учитывать границы расширенных графем-кластеров.
У Collection свойство count концептуально связано с расстоянием от startIndex до endIndex. Для коллекций с произвольным доступом, например Array, это расстояние вычисляется за O(1). Для обычной коллекции оно может вычисляться последовательным перемещением индекса и занимать O(n).
String является коллекцией Character. Элемент Character представляет расширенный графем-кластер, поэтому его нельзя надёжно получить простой индексацией по байтам или Unicode scalar. Подсчёт text.count может пройти по строке, определяя границу каждого такого элемента.
Код напечатает два раза 10000, однако одинаковый результат не означает одинаковую сложность:
Если нужно только проверить непустоту, используйте isEmpty: это выражение не требует подсчёта всех элементов. Если нужна проверка наличия хотя бы заданного количества элементов, полный count можно заменить ограниченным обходом, например подсчитать не более limit + 1 элементов через prefix.
Нельзя переносить гарантии RandomAccessCollection на любой Collection**. Даже наличие свойства count` не обещает постоянную сложность: конкретная коллекция или её индексы определяют фактическую стоимость операции.
В универсальном валидаторе нужно отклонить пустой набор данных и отдельно проверить его содержимое. Вариант с collection.count == 0 функционально корректен, но не выражает намерение и потенциально выполняет полный обход.
Вариант с collection.isEmpty лучше для проверки пустоты: он обычно сразу проверяет начало и конец коллекции. Если требуется проверить минимум 100 элементов, можно использовать ограниченный просмотр первых 101 элементов; его стоимость будет ограничена этим числом, а не полным размером входа.
Полагаться на O(1) для count допустимо только после требования более конкретного протокола или документированной гарантии конкретного типа. Для обобщённого API безопаснее проектировать алгоритм так, чтобы он не вычислял полный размер без необходимости.
isEmpty быстрее count == 0?Для абстрактной Collection isEmpty выражает более узкую операцию и не требует вычисления полного расстояния. Это даёт алгоритму возможность завершиться без обхода. Конкретная реализация может оптимизировать оба варианта одинаково, но у isEmpty нет лишнего требования вычислять размер.
String нельзя индексировать обычным Int?Индекс String.Index учитывает границы допустимых Character, а не произвольные байтовые позиции. Один пользовательский символ может занимать несколько UTF-8 кодовых единиц, поэтому индексирование целым числом могло бы попасть внутрь символа. Абстрактный индекс защищает API от некорректного разрезания текста.
RandomAccessCollection O(1) для любой операции над элементом?Нет. Этот протокол гарантирует постоянную сложность операций перемещения индексов и вычисления расстояния между ними. Получение элемента может дополнительно зависеть от самой коллекции, а операции вроде копирования, преобразования или обработки значения могут иметь собственную стоимость.