Part (II) : What are .map , .compactMap and .flatMap in Swift

Let's dig in our very first and also one of the most useful Higher Order functions in Swift's collections !

It's simple ! Let's start with :

.map :

The whole idea is about mapping collection elements , do something on them and then return the same-sized array of them !

So, here we are creating a new array from the other array . It is done with an O(n) complexity since it iterates through the whole array .

What is O(n)? it's just a simple measure helping us represent the algorithms complexity. Soon I'll publish an article on What is O(n) ? Swifty way and examples to help you understand it better .

Well let's see an example on .map in Arrays to help you understand it better :)

import Foundation

var array = [2, 3, 4, 5]

// 1. Just pass a closure as parameter in .map function and that's that !
let newArray_ModernStyle = array.map { (element) -> Int in
    element * element
}
print(newArray_ModernStyle)  // Result -> [4, 9, 16, 25]

//2. It's what swift offers to help us live better :) $0 acts as (element) in _ModernStyle's map closure.
let newArray_SwiftyWay = array.map { $0 * $0 }
print(newArray_SwiftyWay) // Result -> [4, 9, 16, 25]

//3. What if there weren't any .map in Swift? Oh... S*#!
var newArray_OldFashioned: [Int] = []
for element in array {
    newArray_OldFashioned.append(element * element)
}
print(newArray_OldFashioned) // Result? The same as above! What did you expect?!

O...K, but what about return? To make it easier than what you can find at swift.org :

While having only one line of code inside the closure , our smart complier understands that it's what we wanted to return! element * element is the same as return element * element for the compiler in this case .

Gotcha! Wondering WTH is $0 ?! As it is explained at swift.org :

... $0 and $1 refer to the closure’s first and second arguments... . You'll see $1 in future examples but for now , it's enough to know that $0 simply is the first (in our case the only) argument that .map closure offers ;)

Let's meet .map's brothers - or maybe sisters :) .compactMap and .flatMap !

.compactMap :

Well , as I've already mentioned in the first part of this series , Swift's collection types are implemented as generic types . The same thing goes for also the collections' in-built functions :) It means that there isn't any restrictions on using .map with Array or Array just like Array !

The interesting thing here is that they also work with Optional Types ! What would happen if the .map(ped) array result in some nil values ? Here's where our new Savior shows up :) Ladies & Gentlemen let me introduce you the .compactMap !

import Foundation

var array = ["2", "3", "Four", "Roronoa Zoro"]

let newArray_ModernStyle = array.map { (element) -> Int? in
    Int(element)
}
print(newArray_ModernStyle)

Can you guess the result? It's an Optional array of Ints :) Why Optional? because the Downcast of String to Int might fail - Just like what's happening here . So to make the above example more readable :

...
let newArray_ModernStyle: [Int?] = array.map { (element) -> Int? in //element is a String
    Int(element)
}
print(newArray_ModernStyle) // Result -> [Optional(2), Optional(3), nil, nil]

Wait , wait ... What just happened ?! Do you remember what I said about .map ? ... do something on each element and then return the same-sized array of them ... - Yep! .map should return the same-sized array as the inputed array . In our case some of the Downcasts fail and as we expecting the same-sized array from .map to be thrown , it fills the array with nil as for failed Downcasts .

I guess that you've already guessed ... Here is where .compactMap enters!

To make what developer.apple.com says about it :

.compactMap is almost the same as .map with one difference ; .compactMap discards all of nil results so the newArray contains only valid transformed elements. And here we don't expect the same-sized array to be returned . .compactMap returns an array containing the non-nil results of calling the given transformation with each element of this sequence !

import Foundation

var array: [String] = ["2", "3", "Four", "Roronoa Zoro"]

let newArray_ModernStyle = array.compactMap { (element) -> Int? in
    Int(element)
}
print(newArray_ModernStyle) // Result -> [2, 3]

let newArray_SwiftyWay = array.compactMap { Int($0) }
print(newArray_SwiftyWay) // Result -> [2, 3]

What exactly is Optional behind the scenes ? It's built using Enum with two cases of some value and .none . I'd soon publish an article on What is Optional in Swift ? Behind the scenes to cover the whole thing !

Here is the magic of .compactMap ! As it returns an array containing the non-nil results... it discards the nil and (if needed) unnecessary results just like Optional parts of Int? . To be more clear , it converts the Optional value since it knows that the result can't be nil ,so using Optionals is absolutely useless . But if you declare newArray as type [Int?] in the above example it would produce an array of Optional Ints while holding nils -just like big bro, .map .

It's good to know that the complexity of ,compactMap as developer.apple.com says is O(m +n) . Thats because it iterates through the whole array of n times and produces an array of m items .

By the way, who was that Roronoa Zoro guy in our array ? He is a legend, the greatest swordsman in the whole New World, Vice Captain of Straw Hat Pirates =))))) I deeply suggest you to go and watch One Piece .In the Anime, there is only one thing wrong about Roronoa , and that's he always loses his way and his sense of direction is absolutely awful . This time as he got lost it seems that he were here with us ;)

Let's learn about the last .map's brother -or maybe sister- and wrap this up -so we can go and enjoy the Holy One Piece :)

.flatMap :

It's simple . As of introducing .compactMap beside Swift 4.1 it got even simpler than before .

Nowadays the only thing .flatMap does is to :

"Returns an array containing the concatenated results of calling the given transformation with each element of this sequence." -developer.apple.com

To make it readable by humans ,

"in Swift 4.1+ .flatMap is used only to help you flattening a nested collection into a single array." -Alireza

Well, a nested collection is like an array having another array as its elements . Like an array of arrays !

import Foundation

var array: Array<[Int]> = [[1], [2, 3, 4, 5], [6, 7]]

let newArray_ModernStyle = array.flatMap { (element) -> [Int] in
    element
}
print(newArray_ModernStyle)

let newArray_SwiftyWay: [Int] = array.flatMap { $0 }
print(newArray_SwiftyWay)

Wondering about the result? It'd be -> [1, 2, 3, 4, 5, 6, 7] . The whole array in flattened by .flatMap to make our newArray .

There are two things here that could be nice to point out . The .flatMap's closure always returns Sequence and it's also good to know that Arrays , Sets , Dictionaries and lots of collection types conform to Sequence Protocol . As developer.apple.com mentions , Sequence is a type that provides sequential, iterated access to its elements. Simply it's a list of values that you can step through one at a time . Consider iterating over a Sequence has complexity of O(n) .

Before putting an end to this topic , just like .compactMap , .flatMap has complexity of O(m + n) ! The reason is simple , isn't it? :)

Next: Part (III) : What is .filter in Swift ?


Thanks a lot for coming this far and reading the article :) As you may know it's a series about Higher Order Functions of Swift's Collection , so make sure to read next articles as well as previous one -if you hadn't already- to completely master this subject . Stay Safe , Code Well & Watch One Piece =)

If you have any questions, suggestions or etc. feel free to contact me at alireza@swiftycode.com . We can also chat about One Piece beside Swift -kidding- on Twitter at @swiftycode_com .