Keep calm and use collections effectively in Swift

This blog post aims to give you some insights on how to access first element from a collection and pros & cons of lazy collections.

Don’t miss out the latestCommencis Thoughts and News.

This blog post aims to give you some insights on how to access first element from a collection and pros & cons of lazy collections.

Don’t miss out the latestCommencis Thoughts and News.

Accessing first element in a collection

I guess every developer reading this post has already tried to access the first element of a collection before.

But, what’s the best way to get it in Swift?

Let’s assume that we live in a world that is ruled by cats and those cats are identified with their IDs, names and eye colours. And in this world, we use Arrays to store cats.

struct Cat {

    let id: Int
    let name: String
    let eyeColor: String
}

And in this world, we use Arrays to store cats.

let mia = Cat(id: 1, name: "Mia", eyeColor: "Green")
let maya = Cat(id: 2, name: "Maya", eyeColor: "Green")
let berlin = Cat(id: 3, name: "Berlin", eyeColor: "Blue")

let catArray = [mia, maya, berlin]

Now, how to access the name of the first cat in the catArray?

The first thing that comes to mind can be calling subzero of the array.

catarray

Well, that looks OK. But what if we try to access the first element of a set instead of the array?

The sets are not ordered. Therefore, maybe this question does not make sense, but you can iterate them. During the iteration the first element of a set becomes accessible.

Then, will you try calling subzero of the set like you did for arrays?

collections in swift

What about startIndex?

startIndex property is available for all collection types. Yet it’s not always safe to use, cause if you try to access the element at the startIndex of an empty collection, you will get an exception.

So, the best way to access the first element of a collection is to use first. Thanks to its optional return type, accessing the first element of a collection is easy and safe..

collections in swift

Lazy Collections

In Swift, higher-order functions are eager.

In eager evaluation, an expression is evaluated as soon as it bounds to a variable.

Eager higher-order functions evaluate the closure, create a new array and return the result immediately. However, if we need an early return or just a specific result, all these calculations and creating a new array might be unnecessary and lowers down the speed. Lazy collections come to our rescue in such cases.

This is what Apple says about lazy collections on their documentation:

A view onto the collection that provides lazy implementations of normally eager operations, such as ‘map’ and ‘filter’.

Use the lazy property when chaining operations to prevent intermediate operations from allocating storage, or when you only need a part of the final collection to avoid unnecessary computation.

Let’s analyse lazy collections with code to deeply understand its advantages.

Talk is cheap; show me the code.

let evenNumbers = [0, 2, 4, 6, 8]

let filteredNumbers = evenNumbers.map { $0 * 2 }
    .filter {
        print("Evaluating item \($0) ")
        return $0 < 10
}

//As i mentioned before, first is optional so i provided a default value
print("Filtered first number \(filteredNumbers.first ?? -1)")

The output of playground is:

As you see, all I need is the first element of the result, but all the array is mapped and filtered. This is a situation that affects performance and we really do not want that to happen.

What about lazy evaluation?

let evenNumbers = [0, 2, 4, 6, 8]

let filteredLazyNumbers = evenNumbers.lazy.map { $0 * 2 }
    .filter {
        print("Evaluating item \($0) ")
        return $0 < 10
}

filteredLazyNumbers.first

The output of playground is:

I have heard the words “vow” of those who are not yet aware of these lazy collections.

Using lazy keyword prevents redundant array allocation in the memory. Not only that, the result of the map and filter operations are not calculated until we try to access the first element of filteredLazyNumbers array.

Every time you try to access the first element, this calculation needs to be done again.

Sorry to say, but the good things sometimes bring a burden in its wake.

As I mentioned before, there is not any redundant cached array allocated in the memory for lazy collection computations. If you try to access any element of a lazy collection, each item in the array will be calculated again. This does not bring any performance advantage when you access the items of lazy collection more than once.

Let me show you that with the code:

let filteredLazyNumbers = evenNumbers.lazy.map{ $0 * 2 }
    .filter {
        print("Evaluating item \($0) ")
        return $0 < 10
}

 print("Filtered lazy number \(filteredLazyNumbers.first ?? -1)")
 print("Filtered lazy number \(filteredLazyNumbers.first ?? -1)")
 print("Filtered lazy number \(filteredLazyNumbers.first ?? -1)")

And output is:

collections in swift

Map and filter calculations are made over again and again. But what if we remove the lazy keyword?

let filteredNumbers = evenNumbers.map { $0 * 2 }
    .filter {
        print("Evaluating item \($0) ")
        return $0 < 10
}

 print("Filtered first number \(filteredNumbers.first ?? -1)")
 print("Filtered first number \(filteredNumbers.first ?? -1)")
 print("Filtered first number \(filteredNumbers.first ?? -1)")

The result is barely surprising. Map and filter closure calculated only once for eager evaluation. When we try to access the first element of filteredNumbers, the result comes from the redundant cached array. So it seems that it not as much as redundant as we think before, but only if you are accessing the items more than once.

This article was written to give some information about the collections we use frequently in our daily lives. I hope everyone who reads the post will like it. I would really appreciate your feedbacks.

Thanks!

Note: This post is based on a WWDC session about Using Collections Effectively

https://developer.apple.com/videos/play/wwdc2018/229