---
title: "Three comparisons the standard library already does"
description: "Finding the first match, ordering by more than one key, and checking an index against a range. The hand-written version of each is longer, and two of the three are also slower."
author: "Omar Albeik"
date: 2019-12-16
type: note
topics: [software, engineering]
language: en
reading_time_minutes: 2
canonical_url: https://omaralbeik.com/en/blog/let-the-standard-library-compare
translation_url: https://omaralbeik.com/ar/blog/let-the-standard-library-compare
source_url: https://omaralbeik.com/en/blog/let-the-standard-library-compare.md
---

# Three comparisons the standard library already does

Three things I keep rewriting by hand and keep finding already in the standard
library. They are unrelated, except that in each case the longer form is the
one I reach for first.

## Finding the first match

```swift
let firstSeven = numbers.filter { $0 == 7 }.first
```

`filter` visits every element and allocates an array to hold the matches, then
`first` takes one and the rest is discarded. On an array of a hundred where the
match is at index two, that is ninety-eight comparisons that are not needed.

```swift
let firstSeven = numbers.first { $0 == 7 }
```

`first(where:)` stops at the first match and allocates nothing. Both are O(n) in
the worst case, so this is not a complexity argument — the difference is that
the cost of one tracks where the match is, and the cost of the other tracks the
length of the sequence.

## Ordering by more than one key

Sort contacts by surname, then by first name within each family. Written out,
it is a chain of comparisons hinging on an equality check that is easy to get
subtly wrong:

```swift
if lhs.surname != rhs.surname {
  return lhs.surname < rhs.surname
}
return lhs.name < rhs.name
```

Tuples of `Comparable` elements are themselves `Comparable`, and the comparison
is lexicographic — exactly the rule you were writing:

```swift
extension Person: Comparable {
  static func < (lhs: Person, rhs: Person) -> Bool {
    (lhs.surname, lhs.name) < (rhs.surname, rhs.name)
  }
}
```

Adding a third key is adding a third element to each tuple. The nested version
grows by four lines and a new chance to compare the wrong pair.

## Checking an index against a range

```swift
if index >= 0 && index < items.count {
```

Two comparisons and an assumption — that indices start at zero, which is true
for `Array` and not for `ArraySlice`, whose indices are inherited from the
array it was sliced from. `~=` is the pattern-match operator, the one `switch`
uses for `case 0..<10`, and it is callable directly:

```swift
if 0..<items.count ~= index {
```

That is shorter, but it is the same zero-based assumption in fewer characters —
it does not fix the slice. `items.indices.contains(index)` does, by asking the
collection for its own bounds instead of constructing them:

```swift
if items.indices.contains(index) {
```

All three are cases where the standard library already provides the operation
and the longer form is habit rather than necessity.
