В чём заключается принципиальное ограничение Sequence по сравнению с Collection при обращении к элементам по позиции?
Sequence описывает последовательный обход через итератор и не обязана предоставлять доступ к элементу по позиции. Поэтому обобщённый алгоритм для Sequence не может рассчитывать на индексы, произвольный доступ или фиксированную стоимость перехода к нужному элементу.
Collection дополнительно предоставляет индексы и операции навигации по ним. Однако даже Collection не гарантирует постоянную стоимость перемещения: такую гарантию даёт более специализированный протокол RandomAccessCollection.
Абстракция Sequence нужна для единого API над источниками, которые можно обрабатывать последовательно: массивами, ленивыми цепочками преобразований, генераторами и потоковыми источниками. Исходная проблема — писать алгоритмы обхода без привязки к конкретному способу хранения данных.
Не все источники имеют естественную позицию, которую можно быстро вычислить. Например, для вычисляемого или потокового источника следующий элемент может быть доступен только после получения предыдущего, поэтому протокол ограничивается операцией получения очередного элемента.
Если обобщённый алгоритм ошибочно предполагает, что любой Sequence поддерживает индексы или быстрый доступ по номеру, он либо не скомпилируется, либо будет вынужден сначала материализовать последовательность в массив.
Материализация может потребовать значительной памяти, запустить все ленивые вычисления сразу и лишить обработку потокового источника её последовательного характера. Кроме того, повторный обход не следует считать гарантированным только потому, что значение имеет тип Sequence.
Минимальная модель Sequence строится вокруг метода makeIterator(), который создаёт итератор. Итератор предоставляет next(): он возвращает очередной элемент или nil, когда источник исчерпан. В этой модели нет обязательных startIndex, endIndex и операции получения элемента по индексу.
Например, обобщённая функция может потреблять последовательность через reduce, не зная, массив это, ленивое преобразование или другой источник:
Здесь функция получает элементы только последовательно. Она не требует предварительного создания массива и может работать с ленивой последовательностью, но после завершения обхода нельзя автоматически предполагать, что источник можно безопасно пройти ещё раз.
Collection добавляет индексную модель: у него есть границы, индекс элемента и операции перехода к следующему индексу. Это позволяет формулировать алгоритмы через позиции, но сложность перехода зависит от конкретного типа. Для связного списка переход на большое расстояние может быть линейным, тогда как RandomAccessCollection обещает постоянную сложность операций перемещения и вычисления расстояния между индексами.
Практическое правило простое: используйте требования Sequence, если алгоритму нужен только однонаправленный обход. Требуйте Collection, когда нужны индексы или модель повторного обхода, и RandomAccessCollection, когда алгоритм критично зависит от быстрого произвольного доступа.
Команде нужно реализовать обобщённую функцию поиска максимального значения. Первый вариант принимает Array, обращается к элементам по числовым позициям и предварительно копирует входные данные в массив. Такой подход прост, но ограничивает переиспользование, создаёт дополнительное хранение и плохо подходит для ленивых источников.
Второй вариант принимает Sequence и выполняет один линейный обход. Он экономит память и работает с большим числом источников, но не может повторно обратиться к уже обработанному элементу или использовать произвольный индекс.
Выбран второй вариант, поскольку задаче нужен только один проход. Если бы алгоритму требовалось многократно сравнивать удалённые элементы по позициям, следовало бы явно потребовать Collection или RandomAccessCollection, а не скрывать материализацию внутри функции. В результате API точнее описывает требования и не навязывает лишние расходы вызывающему коду.
Sequence, что источник обязательно одноразовый?Нет. Sequence лишь не обещает многопроходность. Array можно обходить повторно, а пользовательский тип может возвращать новый независимый итератор при каждом вызове makeIterator(). Но обобщённый код не должен полагаться на повторный обход, если это отдельно не гарантировано контрактом конкретного типа.
Sequence без полного обхода?Нет. У Sequence нет обязательного свойства count, а количество элементов может быть неизвестно до завершения итерации. Подсчёт обычно требует пройти источник целиком и может потребить одноразовую последовательность, поэтому нельзя без последствий сначала посчитать элементы, а затем обработать тот же источник ещё раз.
Collection?Collection предоставляет индексы, но не делает каждое перемещение постоянным по времени. Операции вроде перехода на большое расстояние могут занимать время, зависящее от расстояния. Если алгоритм предполагает быстрые вычисления расстояния и смещения индекса, его следует ограничить RandomAccessCollection; это делает требование явным и предотвращает неожиданное ухудшение производительности.