Механизм reduce относится к функциональным операциям над коллекциями данных и пришёл в Swift из функциональных языков (map-reduce, fold). Исторически эта функция позволяет превратить любую коллекцию в единое агрегированное значение (например, сумма, произведение, объединение строк и т.д.), проходя по всем её элементам и накапливая результат. Базовая проблема, которую он решает — лаконичное, читаемое и небаговое агрегирование данных вместо ручных циклов.
В Swift reduce определён как метод коллекций:
func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) throws -> Result) rethrows -> Result
Это значит, что вы указываете исходное (начальное) значение, а дальше пишете функцию, которая для каждого элемента и текущего аккумулятора возвращает новое агрегированное значение.
Пример кода:
let numbers = [1, 2, 3, 4] let sum = numbers.reduce(0) { $0 + $1 } // 10 let joined = numbers.reduce("") { $0 + String($1) } // "1234"
Ключевые особенности:
Как работает reduce на пустой коллекции?
Reduce применяется к каждому элементу коллекции. Если коллекция пуста — возвращается исходное (начальное) значение, никаких вызовов closure не будет.
Пример кода:
let empty: [Int] = [] let sum = empty.reduce(100) { $0 + $1 } // 100
Можно ли с помощью reduce изменить исходную коллекцию?
Нет, reduce — pure-функция, она не изменяет исходную коллекцию. Все изменения происходят только с аккумулятором.
Какая разница между reduce и reduce(into:)?
reduce(into:) позволяет мутировать аккумулятор при каждом проходе и эффективнее работает с value-типами, где создание новой копии (copy-on-write) дорого.
Пример кода:
let nums = [1, 2, 3] let squares = nums.reduce(into: []) { (result: inout [Int], item) in result.append(item * item) } // squares == [1, 4, 9]
Разработчик хотел просуммировать цены продуктов, хранящихся в массиве products. Использовал reduce с начальным значением 0.0, но closure был устроен так, что иногда возвращал отрицательную сумму при наличии скидок, что привело к некорректным итогам и проблемам с расчетом налога.
Плюсы:
Минусы:
Использована reduce(into:) для создания кэша из массива сущностей по id:
let objects: [Entity] = ... let cache = objects.reduce(into: [:]) { $0[$1.id] = $1 }
Плюсы:
Минусы: