- All posts
- Three comparisons the standard library already does
Three comparisons the standard library already does
first(where:) instead of filter().first. Comparable tuples instead of nested comparisons. indices.contains instead of a hand-rolled range check.
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
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.
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:
if lhs.surname != rhs.surname {
return lhs.surname < rhs.surname
}
return lhs.name < rhs.name
Swift provides comparison operators for tuples like this pair of strings. They compare the elements in order, stopping at the first difference:
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
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:
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:
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.
Which standard-library operation have you discovered after writing the longer version yourself? Send it my way. Share these examples with a Swift developer who might have another to add.