Tuples
In this chapter, you’ll learn how to group multiple values into a single compound value known as a tuple.
You create a tuple by listing its elements, separated by commas and enclosed in parentheses. For example:
var point = (4.3, -13.7)This tuple represents a two-dimensional point. Its type is (Double, Double).
A tuple is heterogeneous, meaning its elements can be of different types:
var response = (404, "Not Found")The type of this tuple is (Int, String).
You can use any type in a tuple, even another tuple type.
Elements
A tuple assigns its elements an index, starting at index 0 for the first element. You use this index, preceded by a dot, to access the elements:
print(response.0)
point.1 = 2.7Of course, you can’t mutate a tuple’s elements if the tuple is a constant:
let origin = (0.0, 0.0)
origin.0 = -1.0 // Invalid!Element names
You can name a tuple’s elements to clarify their meaning:
var secondPoint = (x: 5.7, y: 1.8)This lets you access the elements by name:
secondPoint.x = 2Element names are part of a tuple’s type. The type of secondPoint is (x: Double, y: Double), not just (Double, Double). Fortunately, these types are compatible.
The following assignment is allowed:
secondPoint = pointThis assigns point.0 to secondPoint.x and point.1 to secondPoint.y.
The reverse is also allowed:
point = secondPointThis assigns secondPoint.x to point.0 and secondPoint.y to point.1.
However, tuples with different element names aren’t compatible because Swift assumes they have a different meaning:
var measurement = (width: 1.2, height: 5.6)
secondPoint = measurement The next section describes a technique you can use to perform this assignment differently.
Decomposition
You can decompose a tuple into its elements:
var (code, message) = responseThis declares two variables, code and message, and assigns response.0 to code and response.1 to message.
If you want to assign only some of a tuple’s elements, you can use a wildcard pattern (_) to skip the elements you don’t want:
(_, message) = responseThis assigns only response.1 to message.
You can use decomposition to assign tuples with conflicting element names:
(secondPoint.x, secondPoint.y) = measurementThis assigns measurement.0 to secondPoint.x and measurement.1 to secondPoint.y. The element names of measurement don’t matter here because you’re decomposing the tuple and assigning its elements separately.
You can achieve the same result as follows:
secondPoint = (measurement.width, measurement.height)This creates a new tuple containing the elements of measurement and assigns this tuple to secondPoint. The new tuple doesn’t have element names, so its type is compatible with secondPoint.
Comparison operators
You can use the familiar comparison operators to compare tuples. Set point to its initial value of (4.3, -13.7), then try the following:
point == (4.3, -13.7)
point < (6.4, -15.6)
point > (4.3, -15.6)These operators perform their comparisons from left to right:
- In the first expression, the operator first compares
point.0and 4.3, thenpoint.1and -13.7. Both pairs are equal, so the operator returnstrue. - In the second expression, the operator returns
truebecausepoint.0is less than 6.4, regardless of the remaining elements. - In the third expression, the operator first compares
point.0and 4.3. These are equal, so the operator moves to the second pair of elements.point.1is greater than -15.6, so the operator returnstrue.
The comparison operators are less strict than the assignment operator in that they ignore element names. So you can, for example, check if secondPoint and measurement contain the same values:
secondPoint == measurementSwitching over tuples
A switch statement can test all of the elements in a tuple at once. For example:
switch point {
case (0, 0):
print("This point is the origin.")
case (_, 0):
print("This point is on the x-axis.")
case (0, _):
print("This point is on the y-axis.")
default:
break
}This example uses a tuple pattern. As its name implies, this pattern consists of a tuple of patterns. For example, the tuple pattern (0, _) consists of an expression pattern (0) and a wildcard pattern (_). In the example, the switch statement decomposes point and matches each element against the corresponding pattern.
The next example uses a tuple pattern to implement the currentPlayerHasWon function from Tic-Tac-Toe:
func currentPlayerHasWon() -> Bool {
switch (currentPlayer, currentPlayer, currentPlayer) {
case (topLeft, top, topRight),
(left, center, right),
(bottomLeft, bottom, bottomRight),
(topLeft, left, bottomLeft),
(top, center, bottom),
(topRight, right, bottomRight),
(topLeft, center, bottomRight),
(topRight, center, bottomLeft):
true
default:
false
}
}This code checks if any of the rows, columns, or diagonals are filled with the current player’s symbol. The first case contains a list of tuple patterns that group the values in each row, column, or diagonal. The switch expression matches these patterns against a tuple that contains three copies of the player’s symbol.
A tuple pattern is often combined with a value-binding pattern. A value binding uses let or var to assign (bind) a local name to a matched value. For example, the binding let x matches any value and assigns it the name x. Cases that use value binding often include a where clause to restrict their matches.
Here’s an example:
switch point {
case (0, 0):
print("This point is the origin.")
case (let x, 0):
print("This point is on the x-axis at x=\(x).")
case (0, let y):
print("This point is on the y-axis at y=\(y).")
case let (x, y) where x > 0 && y > 0:
print("This point is in the first quadrant at x=\(x) and y=\(y).")
case let (x, y) where x < 0 && y > 0:
print("This point is in the second quadrant at x=\(x) and y=\(y).")
case let (x, y) where x < 0 && y < 0:
print("This point is in the third quadrant at x=\(x) and y=\(y).")
case let (x, y) where x > 0 && y < 0:
print("This point is in the fourth quadrant at x=\(x) and y=\(y).")
default:
break
}This example shows how you can combine a value-binding pattern with a tuple pattern:
(let x, 0)and(0, let y)are tuple patterns that have a value-binding pattern as one of their elements.let (x, y)is a value-binding pattern that uses a tuple pattern to bind two values. In this case, the keywordletapplies to bothxandy.
Tuples as return values
You can use a tuple to return multiple values from a function. Here’s a function that returns the width and height of a rectangle formed by two given points:
func sizeOfRectangle(
from corner1: (x: Double, y: Double),
to corner2: (x: Double, y: Double)
) -> (width: Double, height: Double) {
let width = abs(corner2.x - corner1.x)
let height = abs(corner2.y - corner1.y)
return (width, height)
}Note
This example uses a different formatting style to improve the readability of the lengthy function declaration.
This function uses tuples for both its parameters and its return type. You call it as follows:
let size = sizeOfRectangle(from: (-2, 2), to: (1, -1))The inferred type of size is (width: Double, height: Double), so you can access its elements by name:
size.width
size.heightType aliases
If you often use the same tuple type, you can declare a type alias for it:
typealias Point = (x: Double, y: Double)
typealias Size = (width: Double, height: Double)This declares Point as an alias for the type (x: Double, y: Double), and Size as an alias for (width: Double, height: Double).
typealias creates shorthands for complex types and introduces type names that are specific to your application. A type alias doesn’t add any functionality, but it can make your code clearer and less verbose:
func sizeOfRectangle(from corner1: Point, to corner2: Point) -> Size {
let width = abs(corner2.x - corner1.x)
let height = abs(corner2.y - corner1.y)
return (width, height)
}In Part IV, you’ll learn how you can declare your own custom types that are more than aliases for existing types.
Up next
In this chapter, you learned how you can use tuples to group values. Tuples have many uses; however, their number of elements is fixed when you declare them. This means you can’t use a tuple to, for example, keep track of the movies you want to watch.
In Arrays and Dictionaries, you’ll learn about types that represent a collection of elements. These collections can grow to accommodate any number of elements you want to store.
Before you can learn about collections, you first need to learn about optionals. This is the topic of the next chapter.
Fundamentals