Swift Ranges

by Dimitris Tasios
8 views

In this article, we’ll see how to make and use numeric ranges in Swift.

1. What Are Ranges in Swift?

In Swift, a range defines a series of consecutive numbers. The number at the lower end of the range is called the lower bound of the range. The number at the upper end of the range is called the upper bound of the range.

The basic syntax of a range in Swift is as follows:

let from1To5 = 1...5 // 1, 2, 3, 4, 5

In the example above, the number 1 is the lower bound, 5 is the upper bound, and the ... is the range operator. The constant from1To5 contains all integers starting from 1 up to 5.

Both the lower and the upper bound can be negative numbers. The only limitation is that the lower bound must be less than the upper bound.

let from6To1 = 6...1 // runtime error
let negativeRange = -25 ... -5 // legal
let semiNegativeRange = -10...10 // also legal

Note that in the negativeRange we have to put whitespaces between the range operator.

2. Variations of the Ranges in Swift

As in mathematics, there are multiple types of ranges. In Swift, we can define a range in one of the following 3 ways:

  1. A closed range.
  2. A half-open range.
  3. Lastly, a one-sided range.

2.1 Closed Ranges

In order to write a closed range in Swift, we use the ... operator, just like in the example above. A closed range includes both its lower and upper bounds. The lower and upper bounds can be equal. In that case, the range will contain only a single number.

let from1To5 = 1...5 // 1, 2, 3, 4, 5
let from5To5 = 5...5 // 5

2.2 Half-Open Ranges

A half-open range uses the ..< symbol. It includes all numbers, except the upper bound. Unlike mathematics, in Swift, there is no half-open range from the lower bound’s side. In the case both the lower and upper bounds are equal, the resulting range will be empty.

let from6To10Excluded = 6..<10 // 6, 7, 8, 9. Same as 6...9
let from6To6Excluded = 6..<6 // empty range

As with the closed range, the lower bound must be less than the upper bound. As a result, the following will produce a runtime error:

let from6To5Excluded = 6..<5 // runtime error

2.3 One-Sided Ranges

Finally, we have the one-sided ranges. Those ranges contain only one of the two bounds; so either the lower bound or the upper bound. The bound that is not included is considered “open” and can lead up to great lengths. You might say that the open bound’s side can go up to “infinity”, in theory. In practice, however, performing a loop with a one-sided range operator (more on that later), would lead the program to run out of memory before it gets even close to infinity.

The first example demonstrates a one-sided range from the upper bound’s side. In other words, a range that leads up to “positive infinity”.

let from5ToInfinity = 5... // [5, +∞)
from5ToInfinity.contains(4) // false
from5ToInfinity.contains(5) // true
from5ToInfinity.contains(4354) // true

The contains function is used on ranges. It accepts an Int returns a Bool, if the range contains the integer. As you can see, as long as a number is greater than or equal to 5, the from5ToInfinity range includes that number.

In Swift, there is a Double “number” denoted to infinity. As mentioned above, contains accepts Int numbers only. Trying to convert Double.infinity into an Int would cause a runtime error:

from5ToInfinity.contains(Int(Double.infinity)) // error, EXC_BAD_INSTRUCTION

Similarly, a one-sided range from the lower bound’s size would look like so:

let fromInfinityToThree = ...3 // (-∞, 3]
fromInfinityToThree.contains(-45345) // true
fromInfinityToThree.contains(3) // true
from5ToInfinity.contains(2) //false

Lastly, a half-open, one-sided range is similar to a one-sided range from the lower bound’s side, but the upper bound is excluded:

let fromInfinityToTenExcluded = ..<10 // (-∞, 10)
fromInfinityToTenExcluded.contains(10) // false

3. Uses of Ranges in Swift

We can use a range in Swift in a few different ways.

3.1 Ranges With for-Loops

Firstly, a range is a core part of a for-loop, as it’s used to iterate through some statements.

let forLoopRange = 3...10
for number in forLoopRange {
    print(number)
}

The output is:

3
4
5
6
7
8
9
10

In the example above we used a closed range. We can use all kinds of ranges that we discussed before, except for one-sided ranges from the lower bound’s side. That is because the loop has to know where to start. So, we cannot use the following:

let invalidForLoopRange = ...10
for number in invalidForLoopRange { // compile error here
    print(number)
}

The other type of one-sided range is acceptable. Just be careful, because, if you do not break the loop at some point, the loop will never end. We do not recommend trying to run the following in Playground, as you might need to terminate Xcode.

let infiniteForLoopRange = 10...
for number in infiniteForLoopRange {
    print(number)
}

3.2 Ranges With Arrays

Additionally, we may use ranges in arrays, in order to access multiple elements at once. Instead of accessing one element of an array in square brackets ([]) by using an integer (its index), we can use a range instead. The result of that operation is an array containing the elements whose indices match the numbers of the range.

In the example below we have included various cases that use that show how each range type behaves if used in an array.

let letters = ["A", "B", "C", "D", "E", "F"]

print(letters[0...2]) // ["A", "B", "C"] but we'll use the notation "A, B, C" for brevity
print(letters[3...5]) // D, E, F

print(letters[0..<4]) // A, B, C, D

print(letters[0...0]) // A
print(letters[0..<0]) // no letters printed

print(letters[...])   // prints all letters
print(letters[..<4])  // same as 0..<4
print(letters[...3])  // same as 0...3 and 0..<4
print(letters[3...])  // same as 3...5

print(letters[..<])   // compile error, invalid syntax
print(letters[...10]) // runtime error, we are out of bounds

4. Conclusion

By now, you should be able to use ranges in Swift without any issues. You can find the source code (Playground) on our GitHub page.

Related Posts