Swift Fundamentals

Arrays and Sets

In the previous episode, we explored some of the common types defined by the Swift standard library. Today, we continue that exploration by taking a close look at collection types. The Swift standard library defines three collection types, arrays, sets, and dictionaries. This episode zooms in on arrays and sets.

While each collection type has a unique set of features, the collection types of Swift's standard library also have a few things in common. The values and keys stored by arrays, sets, and dictionaries are strictly typed. In other words, you cannot insert an integer into an array of strings.

This may seem like a major limitation if you're used to working with weakly typed languages. The benefits, however, far outweigh this limitation. You always know what to expect when you're working with a collection in Swift. That's a significant benefit if your goal is creating a robust codebase.

Arrays

What Are They

Arrays are probably the most common collection type. An array stores an ordered collection of values. As I mentioned earlier, the values stored in an array are of the same type.

Initialization

Working with arrays is straightforward. There are several ways to instantiate an array. In this example, we create an array, fruits, using an initializer. We append a pair of parentheses to the shorthand form of the array's type, [String].

let fruits = [String]()

We can also initialize an array using the array's type like this.

let fruits = Array<String>()

While both examples are equivalent, it's recommended to use the shorthand form because it improves readability and it's less verbose.

Like many other programming languages, Swift supports object literals. You can instantiate an array with an array literal. In this example, we create the fruits array using an array literal with three elements.

let fruits = ["Orange", "Cherry", "Apple"]

There's no need to specify the type of the array. Remember that the compiler can infer the array's type by inspecting the values of the array literal. The array is of type [String], an array of strings.

Adding and Removing Values

Under the hood, an array is a structure. We discuss structures later in Swift Fundamentals. Structures have properties and methods. You can ask an array for the number of elements it contains by inspecting the value of its count property.

fruits.count // 3

Arrays also have an isEmpty property. This is similar to asking an array whether it has any elements.

fruits.isEmpty // false

Mutable Collections

Do you remember the var and let keywords from earlier in this series? The var and let keywords define the mutability of a collection. Let me illustrate this with an example. If you declare an array using the var keyword, the array is mutable. This means that you can add and remove elements from the array.

In this example, we declare an empty array, colors. The array is mutable because we use the var keyword to declare it.

var colors = [String]()

Because the array is mutable, we can add and remove elements. We use the append(_:) method to add an item to the end of the array. Since arrays are ordered collections, we can also insert elements at a specific index using the insert(at:) method.

colors.append("yellow") // ["yellow"]
colors.insert("green", at: 1) // ["yellow", "green"]

Removing elements from an array is similar. We invoke the remove(at:) method, passing in the index of the element we wish to remove. The remove(at:) method returns the element that was removed.

colors.remove(at: 0) // "yellow"

The removeAll() method removes every element from the array. The result is an empty array.

colors.removeAll() // []

Immutable Collections

Earlier we declared the fruits array using the let keyword. If we try to add or remove an element, the compiler throws an error because fruits is immutable. It cannot be modified.

let fruits = ["Orange", "Cherry", "Apple"]

fruits.append("Coconut")

Immutable Collections in Swift

Accessing Values

You already know how to add and remove items from an array. To access the value stored at a particular index, you use subscript syntax. The syntax should look familiar if you have experience with other programming languages, such as Objective-C.

var vegetables = ["Leek", "Carrot", "Cabbage"]

vegetables[0] // "Leek"

You can also use subscript syntax to update the value at a particular index.

var vegetables = ["Leek", "Carrot", "Cabbage"]

vegetables[0] // "Leek"

vegetables[2] = "Kale" // ["Leek", "Carrot", "Kale"]

Be careful, though. If the index is outside the array's existing bounds, a runtime error is thrown.

var vegetables = ["Leek", "Carrot", "Cabbage"]

vegetables[0] // "Leek"

vegetables[2] = "Kale" // ["Leek", "Carrot", "Kale"]

vegetables[3] = "Onion"

Index Out of Bounds

You can access every element of an array using a for-in loop. If you're only interested in the elements of the array, the for-in loop looks like this.

for vegetable in vegetables {
    print(vegetable)
}

There's no need to specify the type of vegetable since the compiler knows vegetables stores a collection of String values.

Sets

What Are They

Sets and arrays have several features in common. They both store a collection of values of the same type. You can add and remove elements if the set or array is mutable. But there are some key differences.

A set is unordered and each element can only appear once in a set. While an array can contain duplicate elements, each value contained in a set is unique. To accomplish this, the values stored in a set need to conform to the Hashable protocol.

We discuss protocols later in this series. For now, remember that a type that conforms to the Hashable protocol has a hash value that the set uses to uniquely identify it. That is how a set can guarantee that every element only appears once in the set.

Initialization

The initialization of a set is a bit more verbose than that of an array because sets don't have a shorthand form. In this example, we create a set of String values.

var fruits = Set<String>()

You can also use an array literal to instantiate a set with zero or more values.

var fruits: Set<String> = []
var fruits: Set<String> = ["Orange", "Cherry", "Apple"]

Because the compiler can infer the type of the set by inspecting the values stored in the array literal, we don't need to specify the type of the set we are creating.

var fruits: Set = ["Orange", "Cherry", "Apple"]

Working With Sets

The methods and properties of a set are similar to those of an array. A set also defines a count and an isEmpty property.

var fruits: Set = ["Orange", "Cherry", "Apple"]

fruits.count // 3
fruits.isEmpty // false

Adding and removing elements is slightly different. Because sets are unordered, the methods to add and remove items look a bit different.

var fruits: Set = ["Orange", "Cherry", "Apple"]

fruits.insert("Pear")
fruits.remove("Apple") // "Apple"

The remove(_:) method returns the value that was removed. If we try to remove a value that doesn't exist, the method returns nil.

fruits.remove("Pineapple") // nil

Working with Sets

Iterating over the values of a set is identical to iterating over the values stored in an array.

for fruit in fruits {
    print(fruit)
}

The Power of Sets

What makes sets unique and powerful is the ability to perform operations on sets. In this example, we define two sets. The sets contain the Twitter handles of the people John and Anna follow on Twitter.

let john: Set = ["_bartjacobs", "cocoacasts", "clattner_llvm"]
let anna: Set = ["gruber", "_bartjacobs", "jesse_squires", "clattner_llvm"]

We can create a third set that combines both sets using the union(_:) method. Notice that the union constant only contains unique values, no duplicates.

let union = john.union(anna) // "clattner_llvm", "gruber", "cocoacasts", "_bartjacobs", "jesse_squires"

We can use the intersection(_:) method to determine which followers John and Anna have in common.

let intersection = john.intersection(anna) // "clattner_llvm", "_bartjacobs"

There are several other operations, such as symmetricDifference(_:) and subtraction(_:). The symmetricDifference(_:) method is the opposite of the intersection(_:) method. It creates a set that contains the values unique to each set.

let uniques = john.symmetricDifference(anna) // "gruber", "cocoacasts", "jesse_squires"

As the name implies, the subtracting(_:) method removes the values of one set from another set.

let subtracted = john.subtracting(anna) // "cocoacasts"

I really enjoy using sets because of their unique traits. For some tasks, sets are more performant than arrays. That's something to keep in mind.

What's Next?

Collection types are the bread and butter of the Swift developer, which means it pays to invest some time in understanding how they work and, more importantly, when to use which collection type for a particular task. You'll find yourself using arrays very often. Sometimes, however, a set is the right tool for the job.

We take a look at dictionaries in the next episode of Swift Fundamentals.

Next Episode "Dictionaries"

Stop Writing Swift That Sucks

Download the Swift Patterns I Swear By