Skip to content

Arrays ​

In Tic-Tac-Toe, you used nine variables to store the game board:

swift
var topLeft = "1"
var top = "2"
var topRight = "3"
var left = "4"
var center = "5"
var right = "6"
var bottomLeft = "7"
var bottom = "8"
var bottomRight = "9"

When you had to read or write a position on the board, you had to find the correct variable to use, which was quite tedious. Surely, there must be a better way to store these values? Of course, there is, and it’s known as an array.

Like tuples, arrays are compound values. However, arrays differ from tuples in several ways:

  • Arrays are homogeneous. All elements in an array must be of the same type. Tuples are heterogeneous; they can contain elements of different types.
  • Arrays are flexible. You can add and remove elements as needed. This is different from tuples, which have a fixed size.
  • Arrays are collections. You can use a loop to iterate over the elements in an array. This isn’t possible with tuples.

Declaration ​

The simplest way to declare an array is to use an array literal, which is a list of values separated by commas and enclosed in square brackets:

swift
var board = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]

This code declares board as an array of strings and initializes it with nine elements. This array can replace the nine variables you used in Tic-Tac-Toe.

An array stores its elements together in memory. Here’s a visual representation of board:

An array holding the numbers 1 through 9

As you can see, each element has an index. The first element has index 0, which means the index of the last element is one less than the number of elements.

Types ​

The type of an array is the type of its elements, enclosed in square brackets. For example, the type of board is [String], an array of strings.

The full name for this type is Array<String>. You’ll rarely use this full name, but it’s worth mentioning because it tells you that there’s a single type Array that powers all arrays.

When you declare an array that starts out empty, you must use a type annotation:

swift
var players: [String] = []

This annotation is required because the compiler cannot infer a type from the empty array literal.

Subscripts ​

You use an element’s index as a subscript to access or mutate that element:

swift
board[0]
board[0] = "X"

Of course, you can only mutate a variable array, not a constant one.

The index in a subscript must be valid:

swift
board[9]  

This results in a crash because there’s no element at index 9 in board; the last element has index 8. This common error is known as an off-by-one error.

Subscripts support ranges. Here’s how you use ranges to access the rows of board:

swift
board[..<3]
board[3..<6]
board[6...]

You can mutate elements in this way too:

swift
board[0...2] = ["X", "X", "X"]

This sets the entire first row to "X".

A subscript can contain any expression that returns a valid index. The following example returns board to its original state:

swift
for index in 1...9 {
  board[index - 1] = "\(index)"
}

Properties, methods, and initializers ​

Not all array operations are possible through subscripts. How do you find the size of an array? How do you add elements to an array? How do you remove elements from an array? To answer these questions, you need to learn a bit more about types.

A type specifies the structure and behavior of its values. For example, type Array specifies how an array stores its elements and what operations it can perform. Some of these operations are exposed as subscripts, but most are properties, methods, or initializers.

Properties ​

A property is a special kind of variable. When a type declares a property, every value of that type has this property. For example, type Array declares a count property that returns the number of elements in the array. You access this property as follows:

swift
board.count
players.count

Of course, each array manages its own properties, so board.count returns the number of elements in board, while players.count returns the number of elements in player.

Here are a few more properties of arrays:

swift
players.isEmpty
board.first
players.last

Here’s what these properties do:

  • isEmpty returns true if the array is empty. This is more efficient than testing if count is zero.
  • first and last return the first and last element of the array, respectively. These properties are optionals and return nil if the array is empty.

Properties can be either read-only or read-write. count, isEmpty, first, and last are read-only. You can affect their values by operating on the array, but you cannot mutate them directly:

swift
board.first = "X"

Methods ​

A method is a special kind of function. When a type declares a method, every value of that type can execute this method.

You’ve already used methods in Basic Operators and Functions. One example from that chapter is the isMultiple(of:) method of type Int:

swift
12.isMultiple(of: 3)

As you can see, you call a method in the same way you access a property, with a dot (.). The value before the dot is the value that will execute the method. The method can read this value, and if it’s a variable, the method can even mutate it.

Here are some of the methods you can call on arrays:

swift
players.append("Dana")
players.insert("Alice", at: 0)
players.remove(at: 1)
players.contains("Alice")
players.sort()
players.sorted()

Here’s what these methods do:

  • append(_:) adds an element to the back of the array.
  • insert(_:at:) inserts an element at the specified index. The element that was at this index, as well as any elements after it, move backward to make room for the new element.
  • remove(at:) removes the element at the specified index. The elements after this index move forward to close the gap.
  • contains(_:) returns true if the array contains the specified element.
  • sort sorts the array in ascending order.
  • sorted returns a sorted copy of the array. The array itself is unaffected.

Some of these methods are only available under certain conditions. For example, contains uses the equality operator (==) to compare elements. Therefore, this method is only available when the equality operator is defined for the type of the elements. Similarly, sort and sorted require the comparison operator (<).

Initializers ​

An initializer is a special function that initializes a value. You’ve already used initializers to convert between types:

swift
let number = Int("123")!
let double = Double(number)

Initializers are easy to recognize because they’re named after the type of value they initialize. This is why their name starts with an uppercase letter.

Type Array includes a useful initializer that creates an array with multiple copies of the same value:

swift
board = Array(repeating: " ", count: 9)

This sets board to an array containing nine spaces.

You’ll learn more about properties, methods, and initializers in Part IV, where you’ll declare your own types. For now, explore the Standard Library documentation to discover the properties, methods, and initializers of the types you already know.

Iteration ​

The valid indices for an array range from zero to one less than count. You can use these indices to iterate over the array.

The following example counts the number of times board contains "X":

swift
var index = 0
var totalX = 0
while index < board.count {
  if board[index] == "X" {
    totalX += 1
  }
  index += 1
}

You can accomplish the same with a for-in loop:

swift
totalX = 0
for index in 0..<board.count {
  if board[index] == "X" {
    totalX += 1
  }
}

This removes the need to store and update the index variable.

The range of valid indices is also available through the indices property:

swift
totalX = 0
for index in board.indices {
  if board[index] == "X" {
    totalX += 1
  }
}

This is safer than using 0..<board.count as it avoids off-by-one errors.

In fact, you can ignore the indices completely and iterate over the elements directly:

swift
totalX = 0
for value in board {
  if value == "X" {
    totalX += 1
  }
}

Here, value is a local constant that is assigned each element of board in turn.

You can simplify this example even further with a where clause:

swift
totalX = 0
for value in board where value == "X" {
  totalX += 1
}

A for-in loop is the preferred way to iterate over an array. You can use it even when you need access to the indices, thanks to the enumerated method. This method returns the contents of the array as tuples, where each tuple contains both the index and the value of an element in the array:

swift
totalX = 0
for (index, value) in board.enumerated() where value == "X" {
  print("Found a match at index \(index).")
  totalX += 1
}

This loop uses the tuple pattern that you learned about in Tuples. The loop reads a tuple returned by board.enumerated() and binds its elements to index and value.

Operators ​

Although you’ll mostly use subscripts and methods to work with arrays, the Standard Library does include some operators for arrays.

Concatenation ​

The + and += operators join (concatenate) arrays. The following example appends three elements to player:

swift
players += ["Dana", "Elise", "Frank"]

This is equivalent to:

swift
players = players + ["Dana", "Elise", "Frank"]

And also to:

swift
players.append("Dana")
players.append("Elise")
players.append("Frank")

Equality ​

You can use the equality operator (==) to test if two arrays contain the same elements in the same order:

swift
board[3...5] == ["X", "X", "X"]

This only works if the equality operator knows how to compare the elements of the arrays. In this case, == can compare strings, so it can also compare arrays of strings.

Up next ​

Arrays are versatile collections that can accommodate any number of elements. However, they aren’t always the best tool for the job. The next chapter introduces a second collection type, dictionaries, and compares it to arrays.