Welcome to part 3 of our series on higher-order functions in Swift! In part 1, I gave you an overview of what higher-order functions are and how to create your own. In the second article, I focused on three related higher-order functions; Map, compactMap, flatMap. This article continues with the most important and commonly used higher-order functions in Swift; Here, you’ll learn about the forEach,filter, and sorted capabilities.
ForEach higher order function
ForEach can use higher-order functions instead of for-in loops to iterate over the elements of a collection. Unlike most other higher-order functions, forEach does not return a new collection; Instead, it simply passes through the original collection and allows its items to be used in repeated operations and tasks.
Let’s look at a very simple example. Consider the following arrays with positive and negative numbers:
var numbers = [5, -2, 11, 18, -28, 17, 21, -19]
Copy the code
Suppose we want to iterate over a number array and print the absolute value of each number. We can do this in for-in loops like this:
for number in numbers {
print("Absolute value of \(number) is \(abs(number))")
}
Copy the code
This traditional approach can be replaced by forEach higher-order functions. All we need to do is access it through the numbers array:
numbers.forEach { number in
print("Absolute value of \(number) is \(abs(number))")
}
Copy the code
The parameters in the closure above represent each item in the source collection when the loop is executed. Most of the time we can omit it and use shorthand parameters instead:
numbers.forEach { print("Absolute value of \($0) is \(abs($0))") }
Copy the code
All of the above fragments will print exactly the same result.
For example, the following code prints all positive numbers in the original array:
numbers.forEach {
if $0 > 0 {
print($0)
}
}
Copy the code
There are some things we can’t use forEach. That is, iterating through the original items at the same time as working on them. If you want to use higher-order functions to update the original collection, you should consider using map instead of forEach; The latter is designed to provide us with each item within the loop, keeping the set of sources immutable.
To understand this, see the next snippet:
for i in 0.. <numbers.count { numbers[i] *= 2 }Copy the code
All it does is double the value of each number in the numbers array. Even if the above did the job perfectly, the following would not work:
numbers.forEach { $0 *= 2 }
Copy the code
Attempting to do so will cause Xcode to display the following error:
Left side of mutating operator isn't mutable: '$0' is immutable
Keep this in mind and don’t try to use forEach.
Dictionaries and collections can also be used with forEach because they are also collection types. As an example, let’s consider keeping the following dictionaries for some European countries and their capitals:
let capitals = ["France": "Paris", "Italy": "Rome", "Greece": "Athens", "Spain": "Madrid"]
Copy the code
We can forEach each key-value pair separately like this:
capitals.forEach { print($0) }
Copy the code
Each loop prints a tuple containing the key and the actual value:
(key: "Italy", value: "Rome")
(key: "France", value: "Paris")
(key: "Greece", value: "Athens")
(key: "Spain", value: "Madrid")
Copy the code
It is also possible to access only keys or values:
capitals.forEach { print($0.value) }
Copy the code
So forEach in short, this is a higher-order function. Use it instead of traditional loops whenever possible because it helps to write shorter, cleaner code.
Filter higher order function
The name of the filter higher-order function almost explains its purpose. When applied to one collection, it returns another collection after filtering the original elements based on the criteria.
Let’s consider the example array in numbers again, assuming that we only want to get negative numbers using the Filter method. In its most extended form, this can be done as follows:
let negatives = numbers.filter { number -> Bool in
return number < 0
}
Copy the code
The return type of the closure is a Bool; The items in the result array are those that satisfy the internal conditions of the body; In this case, the number is less than zero. The arguments to the number closure represent each item in the original collection.
As with other higher-order functions, if return is a single-line statement in a closure, we can omit the explicit set return type and keyword. This means we can make the above code much simpler:
let negatives = numbers.filter { number in
number < 0
}
Copy the code
It becomes much simpler if we use shorthand parameters and write everything on a single line:
let negatives = numbers.filter { $0 < 0 }
Copy the code
All of the above is the same, but the last fragment is the clearest and shortest way to filter the raw numbers.
By the same logic, we can also get all numbers greater than 10:
let positivesOver10 = numbers.filter { $0 > 10 }
Copy the code
A filter, like any other high-level function, is not limited to a collection of primitive data types for its items. It can also be used when the item is of a custom type. Consider the following description of the club membership structure:
struct Member {
enum Gender { case male, female }
var name: String
var age: Int
var gender: Gender
}
Copy the code
Suppose we have the following members:
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)]
Copy the code
Let’s look at some scenarios and examples. First, suppose we want to filter the above content and only get male members. We do this using the extended form of filter shown below:
let males = members.filter { member -> Bool in
return member.gender == .male
}
Copy the code
Or, we could do better with a shorter version of it:
let males = members.filter { $0.gender == .male }
Copy the code
A single line of code returns a new array containing only those elements that satisfy the criteria inside the closure. See that we can access properties of custom types using shorthand parameters as if we had a normal object; $0 is the object in this example.
Continuing with another example, the following returns all female members in their 30s:
let femalesIn30s = members.filter { $0.gender == .female && $0.age >= 30 && $0.age < 40 }
Copy the code
Next up for all male members under 30:
let malesUnder30 = members.filter { $0.gender == .male && $0.age < 30 }
Copy the code
In general, we can write any possible condition in a closure that is valid in the context. For example, the following filters all members and returns only members whose names begin with “S” :
let membersWithS = members.filter { $0.name.starts(with: "S") }
Copy the code
To quickly print and view the contents of each result array, we can use the higher-order functions forEach introduced earlier. This is what happens next in the last array above:
membersWithS.forEach { print($0.name) }
Copy the code
Sean Susan
There is nothing particularly difficult about filter higher-order functions. The only thing you should pay special attention to is the condition you apply to it so that it returns the correct and expected result.
Sorted higher-order functions
The sorted high-order function can be sorted in ascending or descending order in a set. It returns a new collection with sorted items.
To see an example, consider again the array introduced in the first part of the numbers article. Given that we wanted to sort it in ascending order from lowest to highest, sorted could be used like this:
let sorted = numbers.sorted { (num1, num2) -> Bool in
return num1 < num2
}
Copy the code
The two parameters in the closure are the two items being compared at any given point in the sorting process. Comparisons are identified as conditions in the closure body.
Like all other higher-order functions, the argument, return type, and return keyword can be omitted and replaced with shorthand arguments:
let sorted = numbers.sorted { $0 < $1 }
Copy the code
This is much shorter than the previous fragment, and you see that the second element is represented with the $1 shorthand parameter.
Sorted also gives us a faster option to perform comparisons; Indicates the order only, using no arguments at all:
let sorted = numbers.sorted(by: <)
Copy the code
In addition to sorted, there is also sort higher-order function. Although its purpose is also to arrange the items of the collection in ascending or descending order, there are significant differences between the two; Sorted returns a new array of sorted items, while sort sorts the items in the original collection.
The following code sorts all the numbers in an array in descending order by numbers, but does not return a new array; Changes occur on the same array:
numbers.sort(by: >)
Copy the code
The above results are:
[21, 18, 17, 11, 5, -2, -19, -28]
Copy the code
As a recommendation, do not use sort if you want to keep the original collection intact after sorting. Sorted, on the other hand, do not use it if you have a very large collection to sort; This will require a lot of extra memory, so it’s best to use it sort to sort properly.
The last useful piece of information you should notice is what is returned when sorted is applied to dictionaries. Let’s take the dictionaries in several countries and capitals introduced in the first part of this article:
let capitals = ["France": "Paris", "Italy": "Rome", "Greece": "Athens", "Spain": "Madrid"]
Copy the code
To sort cities from A to Z, we can do the following:
let sortedCapitals = capitals.sorted { $0.value < $1.value }
Copy the code
Here’s the interesting part; What sorted is returned is not a new dictionary, but contains an array sort of all key-value pairs of tuples. The code above looks like this:
[(key: "Greece", value: "Athens"), (key: "Spain", value: "Madrid"), (key: "France", value: "Paris"), (key: "Italy", value: "Rome")]
Copy the code
summary
Today I’ve introduced three very common higher-order functions in Swift that are great tools for programming; ForEach, filter, and sorted. When they replace traditional solutions implemented using loops, code can become shorter, clearer, readable, and maintainable.
- Underlying related interview articles (github.com/iOS-Mayday/…
- Resume guides and common algorithms (github.com/iOS-Mayday/…