map is a method often used on container types. For Sequence, it looks like this:

Each element of the sequence is passed to the input function, and the outputs are returned in an array. This is a straight one-to-one transformation, where the resulting array has the same number of elements as the sequence.

map is remarkably versatile. Once you know about it, you'll be seeing uses for it everywhere. Here's how we can use it to perform mathematical operations on arrays of numbers:

There is also a similar function on the sequence that doesn't return anything:

This does the exact same thing as map, except it doesn't return an array, because the input function doesn't return anything. It avoids having to create and return an array of Void (even Void takes up space in an array):

Perhaps surprisingly, we also have map on optionals. This makes sense if you think of an optional as a container of either 0 or 1 elements:

If the optional is nil, map returns nil. If not, the value the optional contains is passed to the input function, and the result is returned in an optional.

This is very useful for initialisers and other functions which return optionals, such as Int(String), which can only create an integer if the string contains one:

Or, if we have an optional delegate we want to pass to a function, but only if it is not nil. The obvious way of doing it is by doing this:

Using map is shorter and more to the point:

What if the function you provide to map returns an array, and you don't want to end up with an array of arrays? The flatMap method on Sequence takes care of that:

The input function takes in an element and returns a sequence of elements, possibly of another type. flatMap runs the input function on each of the original sequence's elements, joins the resulting sequences together, and returns them in an array. You can think of it as first running a normal map, then flattening the resulting sequence of sequences into a normal sequence.

Here's how you can use it to split up an array of ranges into a single array of bounds:

There is also a slightly different method of the same name on Sequence:

Here, the input function returns an optional, even if the sequence does not contain optionals. Every time the input function returns nil, it is ignored. This is more like a combination of map and then filtering out all nil values. The method is misnamed, and will be renamed to compactMap (https://github.com/apple/swift-evolution/blob/master/proposals/0187-introduce-filtermap.md) in Swift 4.1:

["a","1","b","3"].flatMap(Int.init) // [1, 3]

Optional has its own version of flatMap:

func flatMap<U>(_ transform: (Element) throws -> U?) rethrows -> U?

If the optional is nil, flatMap returns nil. If not, the value the optional contains is passed to the input function, and the result is returned. Using this function instead of map avoids getting an optional of an optional in return:

var stringOptional: String?
...
let intOptional = stringOptional.flatMap(Int.init)

reduce is used to produce a single value from a sequence:

It can be used to, for example, multiply all the numbers together:

First, it calls the input function with initialResult and the first element of the sequence. The result is passed to the input function again, together with the next element of the sequence. After going through the entire sequence, the last result from the input function is returned.

There is another version where the result parameter to the input function is inout, in other words, mutable. The input function itself doesn't return anything:

Here is the previous example using this version:

The mutable version is best for producing more complex values, such as arrays. It lets us directly add to one array in place instead of having to create a new array for every run of the input function.

We want to make the code clearer, more concise, and hopefully easier to read.

To use an Xcode playground to make a part of the code in CountedSet from Lesson 4, Collections, more functional.

  1. Duplicate the CollectionsExtra project from Lesson 4, and name the duplicate CollectionsExtraFunc.
  2. Open the new project in Xcode, and go to CountedSet.swift.
  3. Go to the following method:
      public var count: Int {
        var result = 0
        for count in elements.values {
          result += count
        }
        return result
      }
  4. This is the archetypical use case for reduce. Replace the body of the function with this:
        return elements.values.reduce(0, +)

    Beautiful, isn't it?

  5. Next, go to the following function:
      public mutating func insert<S>(contentsOf other: S)
        where S:Sequence, S.Element == Element {
    
        for newelement in other {
          insert(newelement)
        }
      }
  6. One option is to use forEach:
        other.forEach({self.insert($0)})
  7. Preferably, we would use other.forEach(insert) here but it leads to an error message about self being immutable, even though we are in a mutating method.

    There is a merge (https://developer.apple.com/documentation/swift/dictionary/2892855-merge) method on Dictionary that is perfect for us. It takes a sequence of key-value pairs and adds it to the dictionary. Every time it encounters a key that already exists, it passes the current value and the new one to the function we provide, and uses whatever that function returns as the new value:

    First, we convert the other sequence to key-value pairs, which is simple since the count of each element is 1 (we will learn about the lazy property in the next section). And for any keys that already exist, we just need to add their values together with the + operator/function.