# Should there be BidirectionalCollection.dropLast(while:)?

There is Collection.drop(while:) which drops elemenst from the beginning.

Look like Swift Algorithms added BidirectionalCollection.suffix(while:)

I think for symmetry and completeness, there should be BidirectionalCollection.dropLast(while:)

I needed this so I made one like this:

extension BidirectionalCollection {
func dropLast(while predicate: (Element) throws -> Bool) rethrows -> Self.SubSequence {
let suffix = try suffix(while: predicate)
return dropLast(suffix.count)
}
}

is there correct? is there better way?

1 Like

Avoid calling count on a non-RandomAccess collection, by leveraging the fact that a slice shares indices with its base collection:

return self[..<suffix.startIndex]

Or, using only standard library methods:

let i = try lastIndex{ try !predicate(\$0) }

guard let j = i else {
return self[..<startIndex]
}

return self[...j]

I don't understand why this works to return the whole thing as Subsequence:

return self[..<startIndex]   // why is the range result in the entire thing as Subsequence?

is it because ..<startIndex is an empty range? Then shouldn't it be an empty Subsequence?

these two I can understand:

return self[..<endIndex]
return self[...]     // I prefer this form

Thanks for the answer, I learned something new about when not to use count

It returns an empty subsequence.

1 Like

okay, I understand now. The guard ... else { } is executed when every element in the collection match the predicate, so an empty Subsequence is returned.

1 Like

Agreed!

is there a better way?

Your original is nearly perfect:

extension BidirectionalCollection {
func dropLast(while predicate: (Element) throws -> Bool) rethrows -> Self.SubSequence {
let suffix = try suffix(while: predicate)
return self[..<suffix.startIndex]
}
}

I might write @Nevin's suggestion this way:

extension BidirectionalCollection {
func dropLast(while predicate: (Element) throws -> Bool) rethrows -> Self.SubSequence {
let end = try lastIndex{ try !predicate(\$0) }.map(index(after:)) ?? startIndex
return self[..<end]
}
}

should endIndex be startIndex to return an empty collection? I think when lastIndex(where:) is nil, you are returning the whole collection. It should be empty.

1 Like

You're quite right, thanks

I think there is an off-by-one error: when lastIndex(where:) found an index, that index position need to be included in the SubSequence. This is what I did:

extension BidirectionalCollection {
func droppedLast(while predicate: (Element) throws -> Bool) rethrows -> SubSequence {
try lastIndex(where: { try !predicate(\$0) }).map { self[...\$0] } ?? self[..<startIndex]
}
}

The Algorithms package does include this method, with the name trimmingSuffix(while:) (implemented here). The package includes a variety of trimming methods, including trimmingPrefix(while:) — a method that shares the functionality of drop(while:) but is more predictably readable at the call site when written with a trailing closure.

Related to the implementation conversation in this thread, we discovered that there are a couple common operations that are used in implementing this kind of functionality: endOfPrefix(while:) and startOfSuffix(while:) (implemented here). startOfSuffix(while:) handles that off-by-one case without recalculating the trailing index.

2 Likes

Why are endOfPrefix(while:) and startOfSuffix(while:) internal? They seem to be generally useful.

Honestly, we just weren't sure if we had the names right or not. They should probably be public!

Should @young file an issue to request they be made public?

That would be great! @young, feel free to start a thread in the Swift Algorithms discussion, file an issue, or open a PR to make the change. Happy to discuss in any of those places.

Bah, that's what I get for hastyposting. Thanks for fixing my errors!

Here's a more elegant one that additionally shows why it's important to expose the bases of the standard library's adapter types:

extension BidirectionalCollection {
func dropLast(while predicate: (Element) throws -> Bool) rethrows -> Self.SubSequence {
let head = try self.reversed().drop(while: predicate)