The filter
method looks like this:
func filter(_ isIncluded: (Element) throws -> Bool) rethrows -> [Element]
This is a simple method on Sequence,
which we have already used. The input function takes an element of the sequence and returns either false
or true
. filter
returns an array of only those elements for which the input function returns true
:
let numbers = [-4,4,2,-8,0] let negative = numbers.filter {$0<0} // [-4, -8]
Set
and Dictionary
have their own versions of this method, which return a Set or Dictionary, respectively.
Let's look at using the filter
method by following this step:
- filter
page in Functional.playground
and replace the body of the method with one that uses filter
. Make sure the unit test passes afterwards.Here's a hint: when introducing arrays in Lesson 4, we mentioned how to get all the indices of a collection.
Here's the solution:
return indices.filter { self[$0] == character }
map
is a method often used on container types. For Sequence
, it looks like this:
func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]
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:
let numbers = [-4,4,2,-8,0] let squared = numbers.map {$0*$0} // [16, 16, 4, 64, 0]
There is also a similar function on the sequence that doesn't return anything:
func forEach(_ body: (Element) throws -> Void) rethrows
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):
squared.forEach { print($0) }
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:
func map<U>(_ transform: (Element) throws -> U) rethrows -> U?
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:
let textTimesTwo = Int("4").map { $0 * 2 }
Or, if we have an optional delegate we want to pass to a function, but only if it is not nil. T
he obvious way of doing it is by doing this:
if let delegate = delegate { doSomething(with: delegate) }
Using map
is shorter and more to the point:
delegate.map(doSomething)
Now that we had a brief about the map
function, let's see how we can make use of it. Here are the steps to do so:
- map
page in Functional.playground
.text
.range(where predicate: (Element) throws -> Bool)
function to use the optional map
instead of guard let
.Solution 1:
let wordLengths = text.split(separator: " ").map {$0.count}
Solution 2:
return try index(where: predicate).map { start in let end = try self[start..<endIndex] .index(where: { try !predicate($0) }) ?? endIndex return start..<end }
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:
func flatMap<S:Sequence>(_ transform: (Element) throws -> S) rethrows -> [S.Element]
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:
let ranges = [0...2, 5...7, 10...11] let bounds = ranges.flatMap {[$0.lowerBound, $0.upperBound]} // [0, 2, 5, 7, 10, 11]
There is also a slightly different method of the same name on Sequence
:
func flatMap<U>(_ transform: (Element) throws -> U?) rethrows -> [U]
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)
- flatMap
page in Functional.playground
.flatMap
instead of a filter
and a map
.range(between:and:)
function to use flatMap
and map
instead of guard let
.Solution 1:
let inverted = numbers.flatMap { nr in return nr == 0 ? [] : [1.0/Double(nr)] }
Or:
let inverted = numbers.flatMap { nr in return nr == 0 ? nil : 1.0/Double(nr) }
Solution 2:
public func range(between fromElement: Element, and toElement: Element) -> Range<Index>? { return index(of: fromElement) .flatMap { fromIndex in let start = index(after: fromIndex) return suffix(from: start).index(of: toElement) .map { toIndex in start..<toIndex } } }
Or if you want to go all the way:
public func range(between fromElement: Element, and toElement: Element) -> Range<Index>? { return index(of: fromElement) .map(index(after: )) .map(suffix(from: )) .flatMap { suffix in suffix.index(of: toElement) .map { suffix.startIndex..<$0 } } }
reduce
is used to produce a single value from a sequence:
func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) throws -> Result) rethrows -> Result
It can be used to, for example, multiply all the numbers together:
let multiplied = negative.reduce(1) { result, element in result * element }
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:
func reduce<Result>(into initialResult: Result, _ updateAccumulatingResult: (inout Result, Self.Element) throws -> ()) rethrows -> Result
Here is the previous example using this version:
let multiplied2 = negative.reduce(into: 1) { result, element in result = result * element }
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.
Now, follow the given step to implement the reduce
function:
- reduce
page in Functional.playground
. Compute the average using reduce
.Here's the solution:
let average = Double(numbers.reduce(0, +)) / Double(numbers.count)
This is the end of our journey with functional programming. In this section, we described functional programming and worked with four important functions: filter
, map
, flatMap
, and reduce
.
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.
CollectionsExtra
project from Lesson 4, and name the duplicate CollectionsExtraFunc
.CountedSet.swift
.public var count: Int { var result = 0 for count in elements.values { result += count } return result }
reduce
. Replace the body of the function with this:return elements.values.reduce(0, +)
Beautiful, isn't it?
public mutating func insert<S>(contentsOf other: S) where S:Sequence, S.Element == Element { for newelement in other { insert(newelement) } }
forEach
:other.forEach({self.insert($0)})
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:
elements.merge(other.lazy.map { ($0, 1) }, uniquingKeysWith: +)
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.