Adding last(where:) in Swift
For quite a while I didn’t notice Sequence.first(where:) exists. It’s like first, only with a condition. Proposed and implemented by Russ Bishop, by the way. I have now happily migrated from my self-baked findFirst to this method – only to find out today that there’s not last(where:) equivalent.
Makes sense at first, since Sequence is not stride-able backwards. But BidirectionalCollection is, and thus Array.
In a huge collection of stuff, it’s probably too costly to simple call array.reversed().first(where: myPredicate). Here, not even lazy would help since reversed() returns an Array in the new order instead of a ReversedBidirectionalCollection<LazyCollection<Whatever>> or similar. So I’m cautious and prefer to enumerate backwards.
BidirectionalCollection has an indices property. In case of arrays, that’s a CountableRange<Int> which can be reversed far more cheaply. And the Indices associated type of BidirectionalCollection supports reversed(), too, so we can generalize to this:
extension BidirectionalCollection
where Self.Indices.Iterator.Element == Self.Index {
func last(
where predicate: (Self.Iterator.Element) throws -> Bool
) rethrows -> Self.Iterator.Element? {
for index in self.indices.reversed() {
let element = self[index]
if try predicate(element) {
return element
}
}
return nil
}
}
This iterates backwards without reversing the whole collection at first. Works with arrays and other interesting types like Ole Begemann’s SortedArray.
By the way: why on earth would BidirectionalCollection.Indices.Iterator.Element be allowed to ever not equal BidirectionalCollection.Index? Without the “where” clause, the compiler will complain about the subscript: “Cannot subscript a value of type ‘Self’ with an index of type ‘Self.Indices.Iterator.Element’”.