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'".

Browse the blog archive