Add ends(with:)#224
Conversation
| var possibleSuffixIterator = possibleSuffix.reversed().makeIterator() | ||
| for e0 in self.reversed() { | ||
| if let e1 = possibleSuffixIterator.next() { |
There was a problem hiding this comment.
Is there an efficient way to implement this without needing backwards iteration of the possibleSuffix?
The only way I can think of is basically a substring search, and checking if it ends up match against the very end.
If such an algorithm existed, it would allow us to loosen the requirement of possibleSuffix from BidirectionalCollection down to just Sequence.
There was a problem hiding this comment.
If we loosen the requirement to just Collection (so that we have a known count), then we can probably iterate from the middle (starting at self.index(self.endIndex, offsetBy: possibleSuffix.count), and matching until the end. I'm not sure if this is better though, because we need to do this (potentially-non-constant-time) index math.
There was a problem hiding this comment.
Right … I don't imagine that there are many cases where you have a bidirectional collection but you can't make the suffix you're searching for bidirectional.
There was a problem hiding this comment.
I agree.
For completeness, I'll mention an alternative implementation I toyed with, which has different constraints:
selfneeds to beRandomAccessCollectioninstead ofBidirectional(and it does some indexing offsetting, which can be slow if that's notO(1)).- The
possiblePrefixonly needs to be aCollectioninstead of aBidirectionalCollection
extension RandomAccessCollection where Element: Equatable {
@inlinable
public func ends<PossibleSuffix: Collection>(
with possibleSuffix: PossibleSuffix,
by areEquivalent: (Element, PossibleSuffix.Element) throws -> Bool
) rethrows -> Bool {
guard let startOfSuffix = index(endIndex, offsetBy: -possibleSuffix.count, limitedBy: startIndex) else {
return false
}
return try self[startOfSuffix...].elementsEqual(possibleSuffix, by: areEquivalent)
}
}There was a problem hiding this comment.
Just a small addition here is that in this case possibleSuffix probably will be better if constrained to RandomAccessCollection as well, otherwise -possibleSuffix.count is not guaranteed to be O(1).
There was a problem hiding this comment.
One of the pages of the "Guides" should link to this, but I'm not sure which.
I don't see any predicate methods like this elsewhere in the package. Perhaps we should start a new Guides/Predicates.md category?
There was a problem hiding this comment.
any predicate methods like this
Can you say more about what you mean here?
There was a problem hiding this comment.
I was thinking that perhaps this can be the first of several methods that fall under a new "predicates" category, which would contain boolean-returning methods which check for particular qualities in a collection.
One other predicate I've been thinking of is something like someArray.allElementsAreEqual(), which e.g. would be true for [1, 1] but not [1, 2].
If we find other predicates to add to swift-algorithms, we can group ends(with:) among them.
Until then, I'm not sure which "guide" we should document this in.
1163301 to
8524728
Compare
natecook1000
left a comment
There was a problem hiding this comment.
Thanks, @amomchilov! Just a few small notes here.
| /// collection are equivalent to the elements in another collection, using | ||
| /// the given predicate as the equivalence test. | ||
| /// | ||
| /// The predicate must be a *equivalence relation* over the elements. That |
There was a problem hiding this comment.
Does this typo show up elsewhere in the project?
| /// The predicate must be a *equivalence relation* over the elements. That | |
| /// The predicate must be an *equivalence relation* over the elements. That |
There was a problem hiding this comment.
Not from swift-algorithms, but in the standard library itself.
I opened a fix: https://github.com/apple/swift/pull/71871/files
| /// let a = 8...10 | ||
| /// let b = 1...10 | ||
| /// | ||
| /// print(b.ends(with: a)) | ||
| /// // Prints "true" |
There was a problem hiding this comment.
Could we use a string-based example here, instead of a range?
There was a problem hiding this comment.
@natecook1000 I copied this doc from starts(with:) and modified it. I think if we change this, we should starts(with:) to match.
There was a problem hiding this comment.
I'm good with either Arrays or Strings. Perhaps ranges are a bad example, given that some readers might confuse ..< and .... We can pick a more self-evident example that doesn't need to require knowledge of that distinction.
8524728 to
375eb1f
Compare
| /// let a = 8...10 | ||
| /// let b = 1...10 | ||
| /// | ||
| /// print(b.ends(with: a)) | ||
| /// // Prints "true" |
There was a problem hiding this comment.
@natecook1000 I copied this doc from starts(with:) and modified it. I think if we change this, we should starts(with:) to match.
| /// collection and the length of `possibleSuffix`. | ||
| // Adapted from https://github.com/apple/swift/blob/d6f9401/stdlib/public/core/SequenceAlgorithms.swift#L281-L286 | ||
| @inlinable | ||
| public func ends<PossibleSuffix: BidirectionalCollection>( |
There was a problem hiding this comment.
| /// collection and the length of `possibleSuffix`. | ||
| // Adapted from https://github.com/apple/swift/blob/d6f9401/stdlib/public/core/SequenceAlgorithms.swift#L235-L252 | ||
| @inlinable | ||
| public func ends<PossibleSuffix: BidirectionalCollection>( |
There was a problem hiding this comment.
| /// collection are equivalent to the elements in another collection, using | ||
| /// the given predicate as the equivalence test. | ||
| /// | ||
| /// The predicate must be a *equivalence relation* over the elements. That |
There was a problem hiding this comment.
Not from swift-algorithms, but in the standard library itself.
I opened a fix: https://github.com/apple/swift/pull/71871/files
375eb1f to
17dfa21
Compare
| @inlinable | ||
| public func ends<PossibleSuffix: BidirectionalCollection>( | ||
| with possibleSuffix: PossibleSuffix, | ||
| by areEquivalent: (Element, PossibleSuffix.Element) throws -> Bool |
There was a problem hiding this comment.
Since equivalence is dictated by areEquivalent does this need to be constrained to element is Equatable(BidirectionalCollection where Element: Equatable)?
There was a problem hiding this comment.
Nice catch! I moved this overload into its own unconstrained extension.
|
@natecook1000 Is this looking good to merge? |
|
@swift-ci Please test |
This PR adds an end-counterpart to the built-in
Sequence.starts(with:).Checklist