> По какому времени работает функция reduce у массива (iOS, Swift)

Уровень: senior · Роль: mobile · Категория: Технические вопросы

Компании: Яндекс

Стек: iOS, Swift

> Пример ответа

Короткий ответ

reduce у массива работает за O(n) - линейное время, пропорциональное количеству элементов. Это не асимптотическая сложность, а фактическое время выполнения зависит от замыкания: если внутри него O(1), то суммарно O(n). Для пустого массива reduce вернёт начальное значение без вызова замыкания.

Подробное объяснение

reduce - это функция высшего порядка, которая последовательно обходит все элементы массива, накапливая результат. Сложность всегда линейная по количеству элементов, потому что каждый элемент обрабатывается ровно один раз. Однако важно различать:

  • Асимптотическая сложность самого обхода - O(n), где n - длина массива.
  • Сложность замыкания - если внутри замыкания выполняется дорогая операция (например, сортировка или поиск), общая сложность будет O(n * f(n)), где f(n) - сложность операции внутри.

Для reduce с начальным значением (reduce(into:) или классический reduce) количество итераций всегда равно count массива. Исключение - если массив пуст: тогда замыкание не вызывается, и возвращается initialResult.

В Swift reduce реализован через итерацию по IndexingIterator, поэтому нет дополнительных накладных расходов на копирование или рекурсию - всё происходит в одном проходе.

На практике

На практике важно помнить:

  • Для больших массивов reduce с конкатенацией строк или массивов через + может быть квадратичным из-за копирования. Используйте reduce(into:) для мутабельных накопителей.
  • Если замыкание содержит тяжёлые вычисления, это напрямую влияет на общее время - профилируйте, а не полагайтесь на асимптотику.
  • reduce не прерывается досрочно (в отличие от first(where:)), поэтому для поиска с ранним выходом он не подходит.
  • Для параллельной обработки reduce не подходит - используйте reduce только для последовательных операций, либо разбивайте массив вручную.

Пример кода

SWIFT
let numbers = [1, 2, 3, 4, 5]
// O(n) - сумма
let sum = numbers.reduce(0, +)
// O(n) - но с копированием строки на каждой итерации (квадратично для больших массивов)
let joined = numbers.reduce("") { $0 + "\($1)" }
// O(n) - эффективно, использует reduce(into:)
let joinedEfficient = numbers.reduce(into: "") { $0 += "\($1)" }
// O(n * m) - если внутри замыкания дорогая операция
let filtered = numbers.reduce([]) { $0.contains($1) ? $0 : $0 + [$1] }

Как отвечать на собеседовании

Начните с чёткого ответа: O(n). Затем уточните, что это сложность самого обхода, а не всего выражения. Приведите пример, где замыкание делает операцию O(1), и сравните с вариантом, где внутри O(n) - тогда общая сложность O(n²). Упомяните reduce(into:) как оптимизацию для мутабельных накопителей. Если спросят про пустой массив - скажите, что замыкание не вызывается, возвращается initialResult. Для senior-позиции добавьте замечание про невозможность раннего выхода и отличие от for-цикла с break.

Что проверяет интервьюер

Интервьюер проверяет:

  • понимание асимптотической сложности стандартных функций коллекций;
  • умение различать сложность обхода и сложность замыкания;
  • знание особенностей Swift-реализации (reduce(into:), мутабельные накопители);
  • способность оценить реальную производительность на больших данных;
  • понимание ограничений reduce (нет раннего выхода, нет параллелизма).

Типичные ошибки

  • Ответ "O(1)" - путаница с reduce как с одной операцией.
  • Утверждение, что reduce всегда O(n) независимо от замыкания - неверно, если замыкание само по себе дорогое.
  • Игнорирование квадратичного поведения при конкатенации строк или массивов через +.
  • Утверждение, что reduce можно прервать досрочно - это не так.
  • Незнание reduce(into:) - для senior это серьёзный пробел.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью