Higher order functions in Swift

Higher order functions (functions that can either accept functions or closures as arguments, or return a function/closure) are mega useful for writing nice clear succinct code. You can write your own, or there are a bunch all ready to take advantage of, from the Swift standard library.

If you look around the internets the most commonly mentioned higher order functions in Swift are generally map, filter, reduce and sort (or sorted). But there are so many more to play with!

Just take a look at Array – you will also find containsdrop, first, flatMap, forEach, partition  and split, – whaa?  You will never need to for-in loop that array again! That said, with so many fancy tools in your arsonry, sometimes you can pull one out, realize you can’t remember how to use it and resort reinventing the wheel.

Let’s take a look at some of these more obscure higher order functions, and compare how some would need to be coded in a for-in or while loop, but first let’s jog our memory of the big four: map, filter, reduce and sort.

Imagine first we have an array of fruits:

var fruits = ["Banana","Pineapple","Coconut","papaya","Kiwi","Rambutan"]

map

Perform an operation on every element in an array to build another array.

eg. Let’s create another array consisting of all of the fruits in lowercase.
Instead of:

var fruitLowercase:[String] = []
for fruit in fruits {
  fruitLowercase.append(fruit.lowercased())
}
//["Banana", "Pineapple", "Coconut", "papaya", "Kiwi", "Rambutan"]

We can simply use map:

let fruitLowercase = fruits.map { $0.lowercased() }
//["Banana", "Pineapple", "Coconut", "papaya", "Kiwi", "Rambutan"]

filter

Return every element in an array that satisfies a condition.

eg. Let’s say we want all of the fruits that start with A-K.
Instead of:

var fruitAK:[String] = []
for fruit in fruits {
  if ("A"..."K").contains(fruit.characters.first!) {
      fruitAK.append(fruit)
  }
}
//["Banana", "Coconut", "Kiwi"]

We can simply use filter:

let fruitAK = fruits.filter { ("A"..."K").contains($0.characters.first!) }
//["Banana", "Coconut", "Kiwi"]

reduce

Generate a single value by performing an operation on every value of an array.

eg. Let’s say we need to know the letter count of all the fruits.
Instead of:

var totalLetters = 0
for fruit in fruits {
  totalLetters+=fruit.characters.count
}
//40

We can simply use reduce:

let totalLetters = fruits.reduce(0) {$0 + $1.characters.count}
//40

sort(sorted)

Create a sorted version of an array. You don’t need to see an example of manually sorting an array to understand how this works!

Sort an array simply by describing how you want the sort to work. You can do this simply by indicating the direction of the sort with a > or <.

var fruitSorted = fruits.sorted(by: &lt;)
//["Banana", "Coconut", "Kiwi", "Pineapple", "Rambutan", "papaya"]

Notice that “papaya” comes last, as lower case letters come after upper case letters. You may want to ignore capitalization (or localization) differences:

var fruitSorted = fruits.sorted(by: {$0.localizedLowercase &lt; $1.localizedLowercase })
//["Banana", "Coconut", "Kiwi", "papaya", "Pineapple", "Rambutan"]

I go into sorting arrays of strings in more detail here. Of course if you want to sort the array itself rather than return a sorted version of the array, you can use sort.

Of course, there’s more than just the big four – let’s take a look at other higher order functions in Array:

contains

Generate a true/false by checking if any element in your array satisfies a condition. Related to filter, but returns a Bool, rather than an array.

eg. Let’s say we need to know if our fruits array contains a four letter fruit.
Instead of:

var fruitContains4Char = false
for fruit in fruits {
  if fruit.characters.count == 4 {
    fruitContains4Char = true
    break
  }
}
//true

We can simply use contains:

let fruitContains4Char = fruits.contains {$0.characters.count == 4}
//true

drop

Drops elements from your array while a condition is true, stops checking when it encounters an element that shouldn’t be dropped.

eg. Let’s say we want to drop all elements at the beginning of the array that contain the letter ‘a’.
Instead of:

var fruitDropA:[String] = fruits
while fruitDropA.count>0 && fruitDropA.first!.contains("a") {
  fruitDropA.remove(at: 0)
}
//["Coconut", "papaya", "Kiwi", "Rambutan"]

We can simply use drop:

let fruitDropA = fruits.drop { $0.contains("a") }
//["Coconut", "papaya", "Kiwi", "Rambutan"]

first

You’re probably familiar with the first property that retrieves the first element of an array, but did you know you can pass in a condition to get only the first element that meets that condition?

eg. Let’s say we want the first element of the array that contains the letter ‘i’.
Instead of:

var firstFruitI:String?
for fruit in fruits {
  if fruit.contains("i") {
    firstFruitI = fruit
    break
  }
}
//Optional("Pineapple")

We can simply use first:

var firstFruitI = fruits.first { $0.contains("i") }
//Optional("Pineapple")

flatMap

Closely related to map, flatMap automatically removes any nil values from a map call, ensuring that the array returned does not contain optionals.

eg. Let’s say we have an array of String values:

let numbers = ["1","3","pineapple","2"]

And we want to convert these to an Array of Int. The map function would return an Array of Optional Int, and a nil value for “pineapple” that isn’t a number:

let numbersMapped = numbers.map { Int($0) }
//[Optional(1), Optional(3), nil, Optional(2)]

That’s not what we’re after, we want an array of Int!

We could of course do this in a for loop:

var numbersMapped:[Int] = []
for number in numbers {
  if let int = Int(number) {
    numbersMapped.append(int)
  }
}
//[1, 3, 2]

But much easier using flatMap:

let numbersMapped = numbers.flatMap { Int($0) }
//[1, 3, 2]

forEach

The forEach higher order function is a cool tool for your programming arsenal – basically short-hand for the for-in loop.

eg. Let’s say we want print the lowercase version of every fruit in our fruits array.
Instead of:

for fruit in fruits {
  print(fruit.lowercased(), terminator: " ")
}
//banana pineapple coconut papaya kiwi rambutan

We can simply use forEach:

fruits.forEach { print($0.lowercased(), terminator: " ") }
//banana pineapple coconut papaya kiwi rambutan

And three lines become one!

partition

The partition method partitions the elements of your array based on a condition. Elements that meet the condition are placed last in the array.

eg. Let’s say we want all of the fruit in our fruits array that contains the letter “i” to come last. We could do this with a for-in loop:

var partitionedFruit:[String] = []
var partition = 0
for fruit in fruits {
  if fruit.contains("i") {
    partitionedFruit.append(fruit)
  } else {
    partitionedFruit.insert(fruit, at: partition)
    partition+=1
  }
}
fruits = partitionedFruit
//["Banana", "Coconut", "papaya", "Rambutan", "Pineapple", "Kiwi"]

Notice that “Kiwi” and “Pineapple”, the only elements with an “i” are placed at the end of the array. Alternatively, we can do this with just one line of code with the partition method:

fruits.partition(by: { $0.contains("i") })
//["Banana", "Rambutan", "Coconut", "papaya", "Kiwi", "Pineapple"]

Notice that this method, rather than returning a new array, actually changes the array itself. Notice also that the order of the elements within each partition changes in a somewhat random fashion.

split

You may be familiar with the components method on String, used to split a String based on a separator.

eg. Let’s say we have a paragraph that we want to split into sentences. We could use the components method, checking for full-stops (aka periods):

let paragraph = "I can't believe it! These higher order functions are like magic. Don't you think? Well, maybe not magic, but pretty useful all the same."

let sentences = paragraph.components(separatedBy: ".")
//["I can\'t believe it! These higher order functions are like magic",
//" Don\'t you think? Well, maybe not magic, but pretty useful all the same",
//""]

What’s with that final element though? An alternative to the components method is the split method, which accepts a separator too, but by default will omit blank elements.

To use the split method on a String, you would use it on the String.characters property, which is a String.CharacterType, which adopts the Collection protocol, giving characters access to many of the same cool higher order functions that Array has access to. Once you’ve separated String characters with split, you’ll have an array of something called a SubSequence, that you can pass in when initializing a String – you can do this on each element of your new array using the map higher order function to end up with an array of Strings.

Phew – what does that look like?

let sentencesSubsequences = paragraph.characters.split(separator: ".")
let sentences = sentencesSubsequences.map{ String($0) }
//["I can\'t believe it! These higher order functions are like magic",
//" Don\'t you think? Well, maybe not magic, but pretty useful all the same",
//""]

But wait, that ignored exclamation marks and question marks – they also define the end of a sentence. How to separate our paragraph using all three?

The split method has a fancy higher order function option as well. You can use it to divide our paragraph by full stops, exclamation marks or question marks:

let sentencesSubsequences = paragraph.characters.split { $0 == "." || $0 == "!" || $0 == "?" }
let sentences = sentencesSubsequences.map{ String($0) }
//["I can\'t believe it",
//" These higher order functions are like magic",
//" Don\'t you think",
//" Well, maybe not magic, but pretty useful all the same"]

Well that’s it! I hope next time you need to perform some magic on an array, you too might remember and take advantage of one of these higher order functions, keep your code nice, pretty and succinct and remember that you don’t need to reinvent the wheel!

Interactive developer, Author - iOS development with Swift - book coming 2017 https://manning.com/books/ios-development-with-swift

Tagged with:
Posted in Swift

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: