Lars Lockefeer

Map, filter and reduce in Swift

Having worked with Swift full-time for over half a year, I feel that now is a good time to start sharing bits and pieces on how to write Swift code instead of Objective-C code that happens to be syntactically correct Swift.

We'll start off easy today with an overview of map, filter and reduce, three nifty little methods that greatly reduce the amount of for-loops we write from day to day in Swift.

Having mentioned for-loops, you probably already guessed that map, filter and reduce can be used to perform operations on collections. Let's have a look at how these functions behave on a very simple collection: an array with elements of type T.

To start off our example, let's define an array of integers:

let numbers = [1, 2, 3, 4, 5]

Map

map can be used to transform a collection. It takes a function t: T -> U and returns a collection containing the results of calling t on each element of the collection. We could use this method to multiply all elements in our array by 2:

let multipliedNumbers = numbers.map { elem in
  return elem * 2
}
// multipliedNumbers = [2, 4, 6, 8, 10]

Using Swift's syntactical sugar, we can make a few optimizations here. First of all, the parameter that is passed to the function t can be referred to with the shorthand syntax $0. Second, since t only consists of a single line, we can omit the return statement, yielding the following one-liner:

let multipliedNumbers2 = numbers.map { $0 * 2 }

The full signature of the map function is: map(t: T -> U) -> [U]. Note that the type of the resulting collection is not equal to the type of the collection it received as input. So rather than transforming integers into integers as we did in our example, we could transform them into any other type.

Filter

filter can be used to filter elements out of a collection depending on a condition. It takes a function i: T -> Bool and returns a collection containing those results in the original collection for which i returns true. Hence, the full signature of the filter function is: filter(i: T -> Bool) -> [T].

Now, let's use that to filter our collection by keeping only the even numbers:

let evenNumbers = numbers.filter { $0 % 2 == 0 }
// evenNumbers = [2, 4]

Reduce

With the reduce method, all values in a collection can be combined into a single value. The function takes two parameters: the initial value i: U and a function c: (U, T) -> U that takes the accumulated value and an element of the collection as a parameter. The full signature of the reduce method is: reduce(i: U, c: (U, T) -> U) -> U.

Let's use this method to calculate the sum of all elements in our array of numbers:

let sum = numbers.reduce(0) { $0 + $1 }
// sum = 15

It may not be immediately clear what is happening here, so let's look at the first two iterations. At the first pass, c is called with the initial value 0 and the first element of the array (1) as parameters. By definition, it calculates the sum of these 2: 0 + 1 = 1 which is then passed on to the next iteration. In this next iteration, c will again be executed, this time with the accumulated value (1) and the second element (2) as parameters, returning the sum: 1 + 1 = 2. This procedure continues until the last element of the array is reached, at which point the accumulated value will simply be returned.

Chaining the methods together

The full power of map, filter and reduce becomes especially apparent when these methods are chained together. Have a look at the following example:

let someNumbers = [1, 2, 3, 4, 5]
let result = someNumbers.filter {
  $0 % 2 == 0
}.map {
  $0 + 10
}.reduce(0) {
  $0 + $1
}

Here we remove the odd numbers from the array with filter. Then, we offset each of them by 10 and calculate the sum of all elements. Verify for yourself that, after executing these chain of methods, result should be set to 26.

That's it for today! Stay tuned for more Swifty bits and pieces.

© 2020 Lars Lockefeer