Welcome to the third part of the series about higher order functions in Swift! In the first part I went through a general presentation of what a higher order function is, and how you can create your own. In the second post I focused on three related higher order functions; map
, compactMap
, flatMap
. This post keeps going with the presentation of the most important and most used higher order functions in Swift; here you will find out about forEach
, filter
and sorted
functions.
The forEach higher order function
forEach
higher order function can be used in place of for-in
loops in order to iterate through the elements of a collection. Unlike most of the other higher order functions, forEach
does not return a new collection; instead it just goes through the original collection and allows to use its items in repetitive operations and tasks.
Let’s see a really simple example. Consider the following array with positive and negative numbers:
1 2 3 |
var numbers = [5, –2, 11, 18, –28, 17, 21, –19] |
Let’s suppose that we want to go through the array and print the absolute value of each number. We can do that in a for-in
loop like that:
1 2 3 4 5 |
for number in numbers { print(“Absolute value of \(number) is \(abs(number))”) } |
That traditional way can be replaced by the forEach
higher order function. All we have to do is to access it through the numbers
array:
1 2 3 4 5 |
numbers.forEach { number in print(“Absolute value of \(number) is \(abs(number))”) } |
The argument in the above closure represents each item in the source collection while performing the loop. Most of the times we can omit it and use the shorthand argument instead:
1 2 3 |
numbers.forEach { print(“Absolute value of \($0) is \(abs($0))”) } |
All the above snippets will print exactly the same result.
Of course, it’s also possible to apply custom logic and conditions in the closure’s body of the forEach
. For instance, the following code prints all positive numbers in the original array:
1 2 3 4 5 6 7 |
numbers.forEach { if $0 > 0 { print($0) } } |
There is something that we cannot do using forEach
. That is, to act on the original items while iterating through them. If you want to use a higher order function in order to update the original collection, then you should consider using map
, but not forEach
; the latter aims to provide us with each item inside a loop, keeping the source collection immutable.
To understand that, see the next snippet:
1 2 3 4 5 |
for i in 0..<numbers.count { numbers[i] *= 2 } |
All it does is to double the value of each number in the numbers
array. Even though the above will perfectly do the job, the following won’t work:
1 2 3 |
numbers.forEach { $0 *= 2 } |
Trying to do so will make Xcode show the following error:
Left side of mutating operator isn't mutable: '$0' is immutable
Keep that in mind, and don’t try to update elements in the original collection using a forEach
.
Dictionaries, as well sets, can also be used with forEach
, as they are collection types as well. To see an example, let’s consider the following dictionary that keeps a few European countries and their capital cities:
1 2 3 |
let capitals = [“France”: “Paris”, “Italy”: “Rome”, “Greece”: “Athens”, “Spain”: “Madrid”] |
We can use forEach
to print each key-value pair separately like so:
1 2 3 |
capitals.forEach { print($0) } |
Each loop prints a tuple containing both the key and the actual value:
1 2 3 4 5 6 |
(key: “Italy”, value: “Rome”) (key: “France”, value: “Paris”) (key: “Greece”, value: “Athens”) (key: “Spain”, value: “Madrid”) |
It’s also possible to access the keys, or the values only:
1 2 3 |
capitals.forEach { print($0.value) } |
So, this is the forEach
higher order function in a nutshell. Use it instead of the traditional loops when possible, as it helps to write shorter and cleaner code.
The filter higher order function
The name of the filter
higher order function pretty much explains its purpose. When applied to a collection, it returns another one after having filtered original elements based on a condition.
Let’s consider once again the example numbers
array from the previous part, and let’s suppose that we want to get only the negative numbers using the filter
method. In the most expanded form, this can be done as follows:
1 2 3 4 5 |
let negatives = numbers.filter { number -> Bool in return number < 0 } |
The return type of the closure is a Bool value; items in the resulting array are those that satisfy the condition inside the body; in this case, numbers lower than zero. The number
argument of the closure represents each item in the original collection.
As it happens with other higher order functions, we can omit explicitly setting the return type, as well as the return
keyword if it’s a single-line statement in the closure. That means that we can write the above simpler as so:
1 2 3 4 5 |
let negatives = numbers.filter { number in number < 0 } |
It can become even simpler if we use the shorthand argument and write everything in one line only:
1 2 3 |
let negatives = numbers.filter { $0 < 0 } |
All the above are equal, however the last snippet is the most clear and shortest way to filter the original numbers.
In the same logic, we can also get all numbers that are higher than 10:
1 2 3 |
let positivesOver10 = numbers.filter { $0 > 10 } |
filter
, like any other higher function, is not limited in collections whose items are of basic data types. It can be used when items are of custom types as well. Consider the following structure that describes a member of a club:
1 2 3 4 5 6 7 8 9 |
struct Member { enum Gender { case male, female } var name: String var age: Int var gender: Gender } |
Let’s say that we have the following members:
1 2 3 4 5 6 7 8 9 10 |
let members = [Member(name: “John”, age: 35, gender: .male), Member(name: “Bob”, age: 32, gender: .male), Member(name: “Helen”, age: 33, gender: .female), Member(name: “Kate”, age: 28, gender: .female), Member(name: “Sean”, age: 27, gender: .male), Member(name: “Peter”, age: 39, gender: .male), Member(name: “Susan”, age: 41, gender: .female), Member(name: “Jim”, age: 43, gender: .male)] |
Let’s see a few scenarios and examples. To start, let’s suppose that we want to filter the above and get male members only. We do that with the expanded form of filter
as shown right next:
1 2 3 4 5 |
let males = members.filter { member -> Bool in return member.gender == .male } |
Or, we can do even better with its shorter version:
1 2 3 |
let males = members.filter { $0.gender == .male } |
Just a single line of code returns a new array that contains only those elements that satisfy the condition inside the closure. See that we can access a property of the custom type through the shorthand argument, exactly as if we had a normal object; $0
is the object in this case.
To continue with another example, the following returns all female members that are in their 30s:
1 2 3 |
let femalesIn30s = members.filter { $0.gender == .female && $0.age >= 30 && $0.age < 40 } |
And the next one all male members under 30:
1 2 3 |
let malesUnder30 = members.filter { $0.gender == .male && $0.age < 30 } |
Generally, we can write any possible condition in the closure that is valid in the context. As an example of that, the following filters all members and returns only those where their names starts with “S”:
1 2 3 |
let membersWithS = members.filter { $0.name.starts(with: “S”) } |
In order to quickly print and see the contents of each resulting array we can use the forEach
higher order function presented earlier. This is what exactly is happening next regarding the last array above:
1 2 3 |
membersWithS.forEach { print($0.name) } |
Sean
Susan
There is nothing particularly difficult regarding the filter
higher order function. The only thing you should take great care about is the conditions you apply in it, so it returns back the correct and expected results.
The sorted higher order function
The sorted
higher order function can be used in order to sort a collection in ascending or descending order. What it returns back is a new collection with the items sorted.
To see an example, consider once again the numbers
array presented in the first part of this post. Supposing that we want to sort it in ascending order and go from the lowest to the highest value, sorted
can be used like so:
1 2 3 4 5 |
let sorted = numbers.sorted { (num1, num2) -> Bool in return num1 < num2 } |
The two arguments in the closure are the two items that are being compared at any given moment during sorting. Comparison is determined as a condition in the closure’s body.
Similarly to all other higher order functions, arguments, return type, as well as the return
keyword can be omitted and use shorthand arguments instead:
1 2 3 |
let sorted = numbers.sorted { $0 < $1 } |
That’s much shorter than the previous snippet, and see that the second element is represented with the $1
shorthand argument.
sorted
gives us a faster option to perform the comparison as well; to indicate the order only, without using any arguments at all:
1 2 3 |
let sorted = numbers.sorted(by: <) |
Besides sorted
, there is also the sort
higher order function. Even though its purpose is to place the items of a collection in ascending or descending order too, there is a great difference between the two; sorted
returns a new array with the sorted items, while sort
sorts items in the original collection.
The following code sorts all numbers in the numbers
array in descending order, but it does not return a new array; changes happen in place on the same array:
1 2 3 |
numbers.sort(by: >) |
The above results to:
1 2 3 |
[21, 18, 17, 11, 5, –2, –19, –28] |
As an advice, do not use sort
if you want to have the original collection intact after sorting. On the other hand, do not use sorted
if you have a really big collection to sort; that would require much additional memory, so it would be better to use sort
in order to sort in place.
One last useful piece of information you should be aware about, is what sorted
returns when applied to dictionaries. Let’s use as example the dictionary with the few countries and capitals presented in the first part of this post:
1 2 3 |
let capitals = [“France”: “Paris”, “Italy”: “Rome”, “Greece”: “Athens”, “Spain”: “Madrid”] |
To sort it based on the city names from A to Z we can do the following:
1 2 3 |
let sortedCapitals = capitals.sorted { $0.value < $1.value } |
The interesting part is here; what sorted
returns is not a new dictionary, but an array of tuples containing all key-value pairs ordered. The above code results to this:
1 2 3 |
[(key: “Greece”, value: “Athens”), (key: “Spain”, value: “Madrid”), (key: “France”, value: “Paris”), (key: “Italy”, value: “Rome”)] |
Summary
Today I presented three quite common higher order functions in Swift that are great tools in programming; forEach
, filter
and sorted
. When they are used in place of traditional solutions implemented using loops, code can become much shorter, cleaner, readable and maintainable. As a reminder, this is the third post in the series of higher order functions in Swift. You can find a general introduction in the first post, and a discussion about map
, compactMap
and flatMap
in the second post. More higher order functions will be presented in the next article. Until then, take care and thanks for reading!