Effective Ranges in Swift

8 Mar 2020

Expressing a range of values is an extremely common concept when programming, which is why I *love* that Swift includes a robust collection of types to represent ranges as part of its standard library. No more ad-hoc utility types for ranges with slightly different semantics scattered around your codebase, or having to choose between a number of third-party implementations (*cough* Java).

Swift ranges are actually pretty powerful, so here are five tips that'll let you work with them more effectively.

`Range`

vs `ClosedRange`

There are actually *two* types of complete range in the standard library: `Range`

, and `ClosedRange`

.

They're very similar, but the semantics of the ranges that they represent differ; whereas the range represented by a `Range`

*does not* include its `upperBound`

, that represented by a `ClosedRange`

*does*.

So when should you use either? One common use of `Range`

is iterating over a number of integer indexes, which may be 0-indexed. In this case, the index for the count of the elements itself is *not* valid, so we don't want to include it.

```
// `..<` gives us a `Range`.
for i in 0..<userCount {
// Fetch the user at `i`.
}
```

On the contrary, I typically find `ClosedRange`

useful when working with `Date`

s (or any form of timestamp). If you want to express an *inclusive* time range, then `ClosedRange`

is the way to go.

```
// `...` gives us a `ClosedRange`.
for day in today...nextSunday {
// Process the day. `nextSunday` will be included!
}
```

Even if you're just using the `lowerBound`

and `upperBound`

properties internally (in which case both ranges perform identically), ensuring that you expose the correct range type as part of your API makes the range semantics of your operation clear.

Did I imply that there are only two types of ranges? I lied - there are actually *5*.

What are those funny partial ranges? They're exactly what you'd imagine - open ranges where you specify one bound, but not the other.

`PartialRangeFrom`

is an open range where you just specify the lower bound. *All* possible values greater than or equal to that are semantically included in the range. As such, it has no `upperBound`

property.

```
// `...` gives us a `PartialRangeFrom`.
let todayOrLater = today...
```

`PartialRangeUpTo`

is effectively a `Range`

where you only specify the upper bound (so there's no `lowerBound`

). *All* possible values less than that bound are semantically included in the range.

```
// `..<` gives us a `PartialRangeUpTo`.
let beforeToday = ..<today
```

`PartialRangeThrough`

is its companion (similar to the `Range`

/`ClosedRange`

split). Again, you only specify the upper bound, but *all* possible values less that *or equal to* that bound are semantically included in that range.

```
// `...` gives us a `PartialRangeThrough`.
let beforeEndOfWeek = ...sunday
```

(I promise there are no more range types to cover).

**So!** In summary,

`Range`

:`lowerBound..<upperBound`

(lowerBound <= x < upperBound)`ClosedRange`

:`lowerBound...upperBound`

(lowerBound <= x <= upperBound)`PartialRangeFrom`

:`lowerBound...`

(lowerBound <= x)`PartialRangeUpTo`

:`..<upperBound`

(x < upperBound)`PartialRangeThrough`

:`...upperBound`

(x <= upperBound)

All of the ranges covered so far have a `.contains(_:)`

method to verify whether an element is included.

```
let myRange = 1..<5
myRange.contains(2) // true
myRange.contains(5) // false
```

However, there's also a handy operator that you can use for the same purpose. (Saying that, I *always* get the order of the range and the value to check wrong).

```
let myRange = 1..<5
myRange ~= 2 // true
myRange ~= 5 // false
```

`Collection`

sBoth `Range`

and `ClosedRange`

are fully-fledged `Collection`

s themselves, whenever the bound is *stridable* using integer increments, anyway (a fancy way of saying, the jump between elements is well defined). This means you can do any of the normal `Collection`

-type interactions with them, such as dropping elements, or taking a count.

```
let range = 1..<5
range.dropLast(2) // 1..<3
range.count // 4
```

Furthermore, you can easily use ranges to initialize another collection of their included elements. The common case is building an array of incrementing integers.

```
// [1, 2, 3, 4, 5, 6, 7, 8, 9]
let values = Array(1..<10)
```

`PartialRangeFrom`

is an infinite `Sequence`

, starting at the lower bound. This means you can iterate over one, however clearly you need to bail out at some point.

```
for x in 3... {
// Need to stop at some point!
break
}
```

`PartialRangeUpTo`

and `PartialRangeThrough`

have no such `Sequence`

/`Collection`

conformance. If you think about it, what's the first element?