Skip to content

Commit

Permalink
Add ends(with:)
Browse files Browse the repository at this point in the history
  • Loading branch information
amomchilov committed Feb 18, 2024
1 parent 82ee335 commit 1163301
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 0 deletions.
37 changes: 37 additions & 0 deletions Guides/EndsWith.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# EndsWith

[[Source](https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/EndsWith.swift) |
[Tests](https://github.com/apple/swift-algorithms/blob/main/Tests/SwiftAlgorithmsTests/EndsWithTests.swift)]

This function checks whether the final elements of the one collection are the same as the elements in another collection.
```
## Detailed Design
The `ends(with:)` and `ends(with:by:)` functions are added as methods on an extension of
`BidirectionalCollection`.
```swift
extension BidirectionalCollection {
public func ends<PossibleSuffix: BidirectionalCollection>(
with possibleSuffix: PossibleSuffix
) -> Bool where PossibleSuffix.Element == Element
public func ends<PossibleSuffix: BidirectionalCollection>(
with possibleSuffix: PossibleSuffix,
by areEquivalent: (Element, PossibleSuffix.Element) throws -> Bool
) rethrows -> Bool
}
```

This method requires `BidirectionalCollection` for being able to traverse back from the end of the collection. It also requires the `possibleSuffix` to be `BidirectionalCollection`, because it too needs to be traverse backwards, to compare its elements against `self` from back to front.

### Complexity

O(*m*), where *m* is the lesser of the length of the collection and the length of `possibleSuffix`.

### Naming

The function's name resembles that of an existing Swift function
`starts(with:)`, which performs same operation however in the forward direction
of the collection.
95 changes: 95 additions & 0 deletions Sources/Algorithms/EndsWith.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Algorithms open source project
//
// Copyright (c) 2021 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
//
//===----------------------------------------------------------------------===//

//===----------------------------------------------------------------------===//
// EndsWith
//===----------------------------------------------------------------------===//

extension BidirectionalCollection where Element: Equatable {


/// Returns a Boolean value indicating whether the final elements of the
/// collection are the same as the elements in another collection.
///
/// This example tests whether one countable range ends with the elements
/// of another countable range.
///
/// let a = 8...10
/// let b = 1...10
///
/// print(b.ends(with: a))
/// // Prints "true"
///
/// Passing a collection with no elements or an empty collection as
/// `possibleSuffix` always results in `true`.
///
/// print(b.ends(with: []))
/// // Prints "true"
///
/// - Parameter possibleSuffix: A collection to compare to this collection.
/// - Returns: `true` if the initial elements of the collection are the same as
/// the elements of `possibleSuffix`; otherwise, `false`. If
/// `possibleSuffix` has no elements, the return value is `true`.
///
/// - Complexity: O(*m*), where *m* is the lesser of the length of the
/// 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>(
with possibleSuffix: PossibleSuffix
) -> Bool where PossibleSuffix.Element == Element {
return self.ends(with: possibleSuffix, by: ==)
}

/// Returns a Boolean value indicating whether the final elements of the
/// 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
/// is, for any elements `a`, `b`, and `c`, the following conditions must
/// hold:
///
/// - `areEquivalent(a, a)` is always `true`. (Reflexivity)
/// - `areEquivalent(a, b)` implies `areEquivalent(b, a)`. (Symmetry)
/// - If `areEquivalent(a, b)` and `areEquivalent(b, c)` are both `true`, then
/// `areEquivalent(a, c)` is also `true`. (Transitivity)
///
/// - Parameters:
/// - possibleSuffix: A collection to compare to this collection.
/// - areEquivalent: A predicate that returns `true` if its two arguments
/// are equivalent; otherwise, `false`.
/// - Returns: `true` if the initial elements of the collection are equivalent
/// to the elements of `possibleSuffix`; otherwise, `false`. If
/// `possibleSuffix` has no elements, the return value is `true`.
///
/// - Complexity: O(*m*), where *m* is the lesser of the length of the
/// 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>(
with possibleSuffix: PossibleSuffix,
by areEquivalent: (Element, PossibleSuffix.Element) throws -> Bool
) rethrows -> Bool {
var possibleSuffixIterator = possibleSuffix.reversed().makeIterator()
for e0 in self.reversed() {
if let e1 = possibleSuffixIterator.next() {
if try !areEquivalent(e0, e1) {
return false
}
}
else {
return true
}
}
return possibleSuffixIterator.next() == nil
}
}

40 changes: 40 additions & 0 deletions Tests/SwiftAlgorithmsTests/EndsWithTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Algorithms open source project
//
// Copyright (c) 2021 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
//
//===----------------------------------------------------------------------===//

import XCTest
import Algorithms

final class EndsWithTests: XCTestCase {
func testEndsWithCorrectSuffix() {
let a = 8...10
let b = 1...10

XCTAssertTrue(b.ends(with: a))
}

func testDoesntEndWithWrongSuffix() {
let a = 8...9
let b = 1...10

XCTAssertFalse(b.ends(with: a))
}

func testEndsWithEmpty() {
let a = 8...10
let empty = [Int]()
XCTAssertTrue(a.ends(with: empty))
}

func testEmptyEndsWithEmpty() {
let empty = [Int]()
XCTAssertTrue(empty.ends(with: empty))
}
}

0 comments on commit 1163301

Please sign in to comment.