Using closures with Swift's array algorithms

In Chapter 3, Using Swift Collections and the Tuple Type, we looked at several built-in algorithms that we could use with Swift's arrays. In that chapter, we briefly looked at how to add simple rules to each of these algorithms with very basic closures. Now that we have a better understanding of closures, let's look at how we can expand on these algorithms using more advanced closures.

In this section, we will primarily be using the map algorithm for consistency purposes; however, we can use the basic ideas demonstrated with any of the algorithms. We will start by defining an array to use:

let guests = ["Jon", "Kim", "Kailey", "Kara"] 

This array contains a list of names and the array is named guests. This array will be used for the majority of examples in this section.

Now that we have our guests array, let's add a closure that will print a greeting to each of the names in the array:

guests.map { name in 
  print("Hello (name)") 
} 

Since the map algorithm applies the closure to each item of the array, this example will print out a greeting for each name within the array. After the first section in this chapter, we should have a pretty good understanding of how this closure works. Using the shorthand syntax that we looked at in the last section, we could reduce the preceding example down to the following single line of code:

guests.map {print("Hello ($0)")}

This is one of the few times, in my opinion, where the shorthand syntax may be easier to read than the standard syntax.

Now, let's say that rather than printing the greeting to the console, we wanted to return a new array that contained the greetings. For this, we would return a String type from our closure, as shown in the following example:

var messages = guests.map {  
  (name:String) -> String in  
  return "Welcome (name)" 
} 

When this code is executed, the messages array will contain a greeting to each of the names in the guests array, while the guests array will remain unchanged. We could access the greetings as follows:

for message in messages { 
  print("(message)") 
} 

The preceding examples in this section showed how to add a closure to the map algorithm inline. This is good if we only had one closure that we wanted to use with the map algorithm, but what if we had more than one closure that we wanted to use, or if we wanted to use the closure multiple times or reuse it with different arrays? For this, we could assign the closure to a constant or variable and then pass in the closure, using its constant or variable name, as needed. Let's look at how to do this. We will begin by defining two closures. One of the closures will print a greeting for each element in the array, and the other closure will print a goodbye message for each element in the array:

let greetGuest = {  
  (name:String) -> Void in 
  print("Hello guest named (name)") 
} 
 
let sayGoodbye = { 
  (name:String) -> Void in 
  print("Goodbye (name)") 
} 

Now that we have two closures, we can use them with the map algorithm as needed. The following code shows how to use these closures interchangeably with the guests array:

guests.map(greetGuest)  
guests.map(sayGoodbye) 

When we use the greetGuest closure with the guests array, the greetings message is printed to the console, and when we use the sayGoodbye closure with the guests array, the goodbye message is printed to the console. If we had another array named guests2, we could use the same closures for that array, as shown in the following example:

guests.map(greetGuest)  
guests2.map(greetGuest)  
guests.map(sayGoodbye)  
guests2.map(sayGoodbye) 

All of the examples in this section so far have either printed a message to the console or returned a new array from the closure. We are not limited to such basic functionality in our closures. For example, we can filter the array within the closure, as shown in the following example:

 

let greetGuest2 = {  
  (name:String) -> Void in 
  if (name.hasPrefix("K")) {  
    print("(name) is on the guest list") 
  } else { 
    print("(name) was not invited") 
 
  } 
} 

In this example, we print out a different message depending on whether the name starts with the letter K or not.

As mentioned earlier in the chapter, closures have the ability to capture and store references to any variable or constant from the context in which they were defined. Let's look at an example of this. Let's say that we have a function that contains the highest temperature for the last seven days at a given location and this function accepts a closure as a parameter. This function will execute the closure on the array of temperatures. The function can be written as follows:

func temperatures(calculate:(Int)->Void) {  
  var tempArray = [72,74,76,68,70,72,66]  
  tempArray.map(calculate) 
 
} 

This function accepts a closure defined as (Int)-> Void. We then use the map algorithm to execute this closure for each item of the tempArray array. The key to using a closure correctly in this situation is to understand that the temperatures function does not know or care what goes on inside the calculate closure. Also, be aware that the closure is also unable to update or change the items within the function's context, which means that the closure cannot change any other variable within the temperature's function; however, it can update variables in the context that it was created in.

Let's look at the function that we will create the closure in. We will name this function testFunction:

func testFunction() {  
  var total = 0 
  var count = 0  
  let addTemps = { 
    (num: Int) -> Void in  
    total += num 
    count += 1 
  } 
  temperatures(calculate: addTemps)  
  print("Total: (total)") 
  print("Count: (count)")  
  print("Average: (total/count)") 
} 

In this function, we begin by defining two variables named total and count, where both variables are of the integer type. We then create a closure named addTemps that will be used to add all the temperatures from the temperatures function together. The addTemps closure will also count how many temperatures there are in the array. To do this, the addTemps closure calculates the sum of each item in the array and keeps the total in the total variable that was defined at the beginning of the function. The addTemps closure also keeps track of the number of items in the array by incrementing the count variable for each item. Notice that neither the total nor count variables are defined within the closure; however, we are able to use them within the closure because they were defined in the same context as the closure.

We then call the temperatures function and pass it the addTemps closure. Finally, we print the total, count, and average temperature to the console. When the testFunction is executed, we will see the following output to the console:

Total: 498 
Count: 7 
Average: 71 

As we can see from the output, the addTemps closure is able to update and use items that are defined within the context that it was created in, even when the closure is used in a different context.

Now that we have looked at using closures with the array map algorithm, let's look at using closures by themselves. We will also look at the ways we can clean up our code to make it easier to read and use.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset