The reduce
function reduces a list into a single value. Often referred to as fold
or aggregate
, it takes two parameters: a starting value and a function.
A function takes a running total and an element of the list as parameters and returns a value that is created by combining the elements in the list.
Unlike map
, filter
, and flatMap
, which would return the same type, reduce
changes the type. In other words, map
, filter
, and flatMap
would take Array
and provide a changed Array
. This is not the case with reduce
as it can change an array to, for instance, a tuple or single value.
Swift provides the reduce
method on arrays and has the following definition:
func reduce<T>(initial: T, @noescape combine: (T, Self.Generator.Element) -> T) -> T
If we use the reduce
method on our numbers
Array
, the result of this call becomes 394
:
let total = numbers.reduce(0) { $0 + $1 }
We could also call reduce
, as follows, as the +
operator is a function in Swift:
let total = numbers.reduce(0, combine: +)
Like the map
and filter
methods, developing a reduce
function is also simple:
func reduce<Element, Value>(elements: [Element], initial: Value, combine: (Value, Element) -> Value) -> Value { var result = initial for element in elements { result = combine(result, element) } return result }
We can achieve the same result (394
) with the following call:
let total = reduce(elements: numbers, initial: 0) { $0 + $1 }
The reduce
method can be used with other types such as arrays of Strings.
The reduction pattern is so powerful that every other function that traverses a list can be specified in terms of it. Let's develop a map
function in terms of reduce
:
func mapIntermsOfReduce<Element, ElementResult>(elements: [Element], transform: Element -> ElementResult) -> [ElementResult] { return reduce(elements: elements, initial: [ElementResult]()) { $0 + [transform( $1 )] } } let result = mapIntermsOfReduce(elements: numbers, transform: { $0 + 2 })
The result is identical to our map
function's result that we developed earlier in this chapter. This is a good example to understand the basics of reduce
.
In the function body, we provide elements
and an initial empty array of ElementResult
, and finally, we provide a closure to combine the elements.
It is also possible to develop a filter
function in terms of reduce
:
func filterIntermsOfReduce<Element>(elements: [Element], predicate: Element -> Bool) -> [Element] { return reduce(elements: elements, initial: []) { predicate($1) ? $0 + [ $1 ] : $0 } } let result = filterIntermsOfReduce(elements: numbers) { $0 % 2 == 0 }
Again, the result is identical to our previously developed filter function.
In the function body, we provide elements, an empty initial array, and finally predicate
as a combinator.
To understand the power of reduce, we can implement the flatMap
function in terms of reduce as well:
func flatMapIntermsOfReduce<Element>(elements: [Element], transform: (Element) -> Element?) -> [Element] { return reduce(elements: elements, initial: []) { guard let transformationResult = transform($1) else { return $0 } return $0 + [transformationResult] } } let anArrayOfNumbers = [1, 3, 5] let oneDimensionalArray = flatMapIntermsOfReduce(elements: anArrayOfNumbers) { $0 + 5 }
Finally, let's implement the flatten
function in terms of reduce
:
func flattenIntermsOfReduce<Element>(elements: [[Element]]) -> [Element] { return elements.reduce([]) { $0 + $1 } }
This function takes a two-dimensional array and converts it to a one-dimensional array. Let's test this function:
let flattened = flattenIntermsOfReduce(elements: [[1, 3, 5], [2, 4, 6]])
The result will be [1, 3, 5, 2, 4, 6]
.