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/…