Get up to 80 % extra points for free! More info:

Lesson 2 - Filtering and mapping arrays in Swift

In the previous lesson, Introduction to collections and genericity in Swift, we explained genericity in Swift.

We're getting to another important part of programming. As you already know from the lesson about arrays in Swift, they are often useful for storing multiple elements of the same type and working with them further. That's why collections exist.

Swift offers three primary collections which are quite straightforward:

  • Array - we already know arrays
  • Dictionary
  • Set

Each collection comes in handy in different situations, but the most used one is probably Array.

Array

Swift implements its Array as a modern collection and it's used widely in this language. Just to jog your memory: we don't have to specify the array size or care about it in any way, we are provided with simple methods for adding items, removing them and several others. We already know the basic ones so we're going to skip them. Today's lesson is going to be all about the advanced array methods.

Let's create a new console application named e.g. Collections and prepare a simple array of numbers in main.swift:

var numbers = [2, 5, 9, 12, 34, 17, 28, 18]

filter()

We often need to "filter" the array and get only those elements which match a given condition. We write this condition in a special block called closure. All we need to know for now is that this block is similar to a function which we can pass to a method as a parameter. Basically, we pass a function to a function. It may sound a bit confusing but if you think about it, it makes sense. For example, we call the filter() method and make it filter the array so it'll contain only the values matching our closure (function). We'll now pass such a closure specifying how to filter our number array to the filter() method. If we start typing numbers.filter and let Xcode to generate the rest of the code, we'll get something like this:

numbers.filter(isIncluded: (Int) throws -> Bool)

We declare closures using this arrow, so now we should be able to declare a method which would take one as an argument. But that's not what we want so we'll confirm by Enter again. Xcode will generate a blank closure. The result looks like this:

numbers.filter { (Int) -> Bool in
code
}

The (Int) -> Bool expression means that we need to check whether a number (Int) should stay in the array or not (Bool). Int in the parenthesis and "code" in the block are highlighted. Now we'll write the variable name representing individual array elements into the parenthesis where Int is written at the moment, so we can further work with the elements. Since we have numbers in our array, let's name the variable number:

numbers.filter { (number) -> Bool in
}

The filter() method executes a closure for each number in the array and depending on the returned Bool value decides whether the number should stay in the filtered array or not. The closure is still empty. Let's say we want only numbers bigger than 10, in that case the block would look like this:

return number > 10

And the whole code like this:

numbers.filter { (number) -> Bool in
    return number > 10
}

If we print the result, we can see that the numbers really match the condition.

print(numbers)

Shortened notation

Maybe you're thinking that's a long code for just filtering an array, since we're just comparing the number with the value of 10. Swift, fortunately, offers a fairly shorter notation, but we have to write it all by ourselves.

numbers.filter { $0 > 10 }

That's much better, isn't it? $0 represents the variable which we named number before, and we don't need return at all. That's because it's clear that the expression is returned and so it's used as the return value automatically. Note that the parentheses are no longer there. This syntax is called trailing closure. If the method had any standard parameters, we'd pass them in parentheses, close it, and than pass the closure. It could look like this:

instance.method("someParameter") { $0 > 10 }

Now when we can filter arrays, let's have a look at the similar yet very useful method, map().

map()

We use the method to transform data to a different data type or different structure. All we need to do in the closure is to define how to transform the existing data. For example, we'll want to multiply all the numbers by two. First, let's see the code generated by Xcode. It should remind you the one for the filter() method:

numbers.map { (Int) -> T in
}

T replaces the specific data type; it's genericity we explained last time. Now all we need to do is to replace T with the data type we want to return. Because we just multiply, the return type stays Int. The whole code of calling the map() method for multiplying the elements by two would look like this:

numbers.map { (number) -> Int in
    return number * 2
}

Of course, we could shorten it using a trailing closure:

numbers.map { $0 * 2 }

Maybe you are thinking we could do all of this in some loop even without knowing closures. However, that would take more effort, we'd have to declare a new array and store the individual elements there, one by one. Besides, it's really straightforward what we're trying to do using filter() and map() when reading the code.

Multiplying numbers is of course a very basic example. A real-life scenario would be, for example to use map() if we had an array of Student objects and wanted to get an array of their email addresses.

In the next lesson, Dictionaries in Swift, we'll discuss dictionaries which Swift implements as the Dictionary class.


 

Previous article
Introduction to collections and genericity in Swift
All articles in this section
Collections in Swift
Skip article
(not recommended)
Dictionaries in Swift
Article has been written for you by Filip Němeček
Avatar
User rating:
No one has rated this quite yet, be the first one!
Activities