Arrays β
In Tic-Tac-Toe, you used nine variables to store the game board:
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:
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
:
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:
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:
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:
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
:
board[..<3]
board[3..<6]
board[6...]
You can mutate elements in this way too:
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:
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:
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:
players.isEmpty
board.first
players.last
Hereβs what these properties do:
isEmpty
returnstrue
if the array is empty. This is more efficient than testing ifcount
is zero.first
andlast
return the first and last element of the array, respectively. These properties are optionals and returnnil
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:
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
:
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:
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(_:)
returnstrue
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:
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:
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"
:
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:
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:
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:
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:
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:
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
:
players += ["Dana", "Elise", "Frank"]
This is equivalent to:
players = players + ["Dana", "Elise", "Frank"]
And also to:
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:
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.