diff --git a/Guides/EndsWith.md b/Guides/EndsWith.md new file mode 100644 index 00000000..49f0e6f7 --- /dev/null +++ b/Guides/EndsWith.md @@ -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( + with possibleSuffix: PossibleSuffix + ) -> Bool where PossibleSuffix.Element == Element + + public func ends( + 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. diff --git a/Sources/Algorithms/EndsWith.swift b/Sources/Algorithms/EndsWith.swift new file mode 100644 index 00000000..bf6432be --- /dev/null +++ b/Sources/Algorithms/EndsWith.swift @@ -0,0 +1,82 @@ +//===----------------------------------------------------------------------===// +// +// 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`. + @inlinable + public func ends( + 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`. + @inlinable + public func ends( + with possibleSuffix: PossibleSuffix, + by areEquivalent: (Element, PossibleSuffix.Element) throws -> Bool + ) rethrows -> Bool { + try self.reversed().starts(with: possibleSuffix.reversed(), by: areEquivalent) + } +} + diff --git a/Tests/SwiftAlgorithmsTests/EndsWithTests.swift b/Tests/SwiftAlgorithmsTests/EndsWithTests.swift new file mode 100644 index 00000000..350f11c1 --- /dev/null +++ b/Tests/SwiftAlgorithmsTests/EndsWithTests.swift @@ -0,0 +1,48 @@ +//===----------------------------------------------------------------------===// +// +// 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 testDoesntEndWithTooLongSuffix() { + XCTAssertFalse((2...5).ends(with: (1...10))) + } + + func testEndsWithEmpty() { + let a = 8...10 + let empty = [Int]() + XCTAssertTrue(a.ends(with: empty)) + } + + func testEmptyEndsWithEmpty() { + let empty = [Int]() + XCTAssertTrue(empty.ends(with: empty)) + } + + func testEmptyDoesNotEndWithNonempty() { + XCTAssertFalse([].ends(with: 1...10)) + } +}