- All posts
- Three comparisons the standard library already does
Three comparisons the standard library already does
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.
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
Tuples of Comparable elements are themselves Comparable, and the comparison
is lexicographic — exactly the rule you were writing:
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.