> Как работает reduce в Swift (iOS, Swift)

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

Компании: 2GIS

Стек: iOS, Swift

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

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

reduce - это метод последовательности в Swift, который сворачивает коллекцию в одно значение, последовательно применяя замыкание к аккумулятору и каждому элементу. Сигнатура: reduce(_ initialResult: Result, _ nextPartialResult: (Result, Element) -> Result) -> Result. Используется для агрегации данных: суммы, произведения, конкатенации строк, построения словарей. Важно помнить, что reduce создаёт новое значение на каждой итерации, поэтому для тяжёлых объектов может быть менее эффективным, чем цикл.

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

reduce - это функция высшего порядка, доступная на всех типах, соответствующих протоколу Sequence. Она принимает два параметра:

  1. initialResult - стартовое значение аккумулятора.
  2. nextPartialResult - замыкание, которое получает текущий аккумулятор и очередной элемент, возвращая новый аккумулятор.

Ключевые особенности:

  • Порядок обхода - элементы обрабатываются последовательно, слева направо.
  • Аккумулятор - тип результата не обязан совпадать с типом элементов коллекции.
  • Чистота - замыкание не должно иметь побочных эффектов, иначе поведение непредсказуемо.
  • Вариант reduce(into:) - модифицирует аккумулятор in-place, что позволяет избежать копирования больших структур (например, словарей).

Сравнение с альтернативами:

  • for цикл - более гибкий, но требует ручного управления аккумулятором.
  • map и filter - не подходят для агрегации, так как возвращают коллекцию того же типа.
  • flatMap - для комбинирования, но не для свёртки.

Типичные сценарии использования:

  • Сумма чисел: [1,2,3].reduce(0, +).
  • Построение строки: ["a","b"].reduce("") { $0 + $1 }.
  • Подсчёт частоты элементов: reduce(into: [:]) { $0[$1, default: 0] += 1 }.
  • Проверка всех элементов: reduce(true) { $0 && condition($1) } - но лучше использовать allSatisfy.

На практике

В реальном коде reduce часто применяется для:

  • Агрегации данных из API (суммирование цен, подсчёт количества).
  • Преобразования массива в словарь или множество.
  • Построения сложных структур (например, дерева) из плоского списка.

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

Производительность: reduce компилируется в эффективный код, но для больших коллекций с тяжёлыми операциями копирования (например, строки) reduce(into:) будет быстрее. Для простых типов разница незначительна.

Пример кода

SWIFT
// Сумма чисел
let sum = [1, 2, 3, 4].reduce(0, +) // 10
// Конкатенация строк
let joined = ["Hello", " ", "World"].reduce("", +) // "Hello World"
// Подсчёт частоты символов
let frequency = "abracadabra".reduce(into: [:]) { counts, char in
counts[char, default: 0] += 1
}
// Построение словаря из массива
let pairs = [(1, "one"), (2, "two")]
let dict = pairs.reduce(into: [Int: String]()) { $0[$1.0] = $1.1 }
// Проверка, что все элементы чётные
let allEven = [2, 4, 6].reduce(true) { $0 && $1 % 2 == 0 } // true
// Сложный пример: группировка по чётности
let grouped = [1, 2, 3, 4, 5].reduce(into: (even: [Int](), odd: [Int]())) { result, number in
if number % 2 == 0 {
result.even.append(number)
} else {
result.odd.append(number)
}
}

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

Начните с краткого определения и сигнатуры. Затем приведите простой пример (сумма чисел). Покажите понимание reduce(into:) и объясните, когда он нужен. Упомяните trade-off между читаемостью и производительностью. Если спросят про альтернативы - сравните с циклом и allSatisfy. Хорошо продемонстрировать знание типичных ошибок: изменение внешнего состояния внутри замыкания, неправильный начальный результат, игнорирование порядка элементов.

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

  • Понимание функционального подхода к обработке коллекций.
  • Умение выбирать правильный инструмент: reduce vs map vs filter.
  • Знание нюансов производительности (reduce(into:)).
  • Способность писать читаемый код без побочных эффектов.
  • Понимание обобщённого программирования и протокола Sequence.

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

  • Использование reduce для простого поиска - лучше contains или first(where:).
  • Мутация внешних переменных внутри замыкания - нарушение чистоты функции.
  • Забывание про reduce(into:) для словарей и массивов - лишние копирования.
  • Неправильный начальный результат: например, reduce(1, *) для пустого массива вернёт 1, а не 0.
  • Перепутывание порядка параметров в замыкании: первый - аккумулятор, второй - элемент.
  • Использование reduce для конкатенации строк - лучше joined(), так как это оптимизировано.

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

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