Skip to content

Commit 401145b

Browse files
authored
Add ends(with:) (#224)
1 parent cdc909b commit 401145b

File tree

3 files changed

+217
-0
lines changed

3 files changed

+217
-0
lines changed

Guides/EndsWith.md

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# EndsWith
2+
3+
[[Source](https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/EndsWith.swift) |
4+
[Tests](https://github.com/apple/swift-algorithms/blob/main/Tests/SwiftAlgorithmsTests/EndsWithTests.swift)]
5+
6+
This function checks whether the final elements of the one collection are the same as the elements in another collection.
7+
```
8+
9+
## Detailed Design
10+
11+
The `ends(with:)` and `ends(with:by:)` functions are added as methods on an extension of
12+
`BidirectionalCollection`.
13+
14+
```swift
15+
extension BidirectionalCollection {
16+
public func ends<PossibleSuffix: BidirectionalCollection>(
17+
with possibleSuffix: PossibleSuffix
18+
) -> Bool where PossibleSuffix.Element == Element
19+
20+
public func ends<PossibleSuffix: BidirectionalCollection>(
21+
with possibleSuffix: PossibleSuffix,
22+
by areEquivalent: (Element, PossibleSuffix.Element) throws -> Bool
23+
) rethrows -> Bool
24+
}
25+
```
26+
27+
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.
28+
29+
### Complexity
30+
31+
O(*m*), where *m* is the lesser of the length of the collection and the length of `possibleSuffix`.
32+
33+
### Naming
34+
35+
The function's name resembles that of an existing Swift function
36+
`starts(with:)`, which performs same operation however in the forward direction
37+
of the collection.

Sources/Algorithms/EndsWith.swift

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Algorithms open source project
4+
//
5+
// Copyright (c) 2021 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
//===----------------------------------------------------------------------===//
13+
// EndsWith
14+
//===----------------------------------------------------------------------===//
15+
16+
extension BidirectionalCollection where Element: Equatable {
17+
18+
19+
/// Returns a Boolean value indicating whether the final elements of the
20+
/// collection are the same as the elements in another collection.
21+
///
22+
/// This example tests whether one countable range ends with the elements
23+
/// of another countable range.
24+
///
25+
/// let a = 8...10
26+
/// let b = 1...10
27+
///
28+
/// print(b.ends(with: a))
29+
/// // Prints "true"
30+
///
31+
/// Passing a collection with no elements or an empty collection as
32+
/// `possibleSuffix` always results in `true`.
33+
///
34+
/// print(b.ends(with: []))
35+
/// // Prints "true"
36+
///
37+
/// - Parameter possibleSuffix: A collection to compare to this collection.
38+
/// - Returns: `true` if the initial elements of the collection are the same as
39+
/// the elements of `possibleSuffix`; otherwise, `false`. If
40+
/// `possibleSuffix` has no elements, the return value is `true`.
41+
///
42+
/// - Complexity: O(*m*), where *m* is the lesser of the length of the
43+
/// collection and the length of `possibleSuffix`.
44+
@inlinable
45+
public func ends<PossibleSuffix: BidirectionalCollection>(
46+
with possibleSuffix: PossibleSuffix
47+
) -> Bool where PossibleSuffix.Element == Element {
48+
return self.ends(with: possibleSuffix, by: ==)
49+
}
50+
}
51+
52+
extension BidirectionalCollection {
53+
/// Returns a Boolean value indicating whether the final elements of the
54+
/// collection are equivalent to the elements in another collection, using
55+
/// the given predicate as the equivalence test.
56+
///
57+
/// The predicate must be an *equivalence relation* over the elements. That
58+
/// is, for any elements `a`, `b`, and `c`, the following conditions must
59+
/// hold:
60+
///
61+
/// - `areEquivalent(a, a)` is always `true`. (Reflexivity)
62+
/// - `areEquivalent(a, b)` implies `areEquivalent(b, a)`. (Symmetry)
63+
/// - If `areEquivalent(a, b)` and `areEquivalent(b, c)` are both `true`, then
64+
/// `areEquivalent(a, c)` is also `true`. (Transitivity)
65+
///
66+
/// - Parameters:
67+
/// - possibleSuffix: A collection to compare to this collection.
68+
/// - areEquivalent: A predicate that returns `true` if its two arguments
69+
/// are equivalent; otherwise, `false`.
70+
/// - Returns: `true` if the initial elements of the collection are equivalent
71+
/// to the elements of `possibleSuffix`; otherwise, `false`. If
72+
/// `possibleSuffix` has no elements, the return value is `true`.
73+
///
74+
/// - Complexity: O(*m*), where *m* is the lesser of the length of the
75+
/// collection and the length of `possibleSuffix`.
76+
@inlinable
77+
public func ends<PossibleSuffix: BidirectionalCollection>(
78+
with possibleSuffix: PossibleSuffix,
79+
by areEquivalent: (Element, PossibleSuffix.Element) throws -> Bool
80+
) rethrows -> Bool {
81+
try self.reversed().starts(with: possibleSuffix.reversed(), by: areEquivalent)
82+
}
83+
}
84+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Algorithms open source project
4+
//
5+
// Copyright (c) 2021 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
import XCTest
13+
import Algorithms
14+
15+
final class EndsWithTests: XCTestCase {
16+
func testEndsWithCorrectSuffix() {
17+
let a = 8...10
18+
let b = 1...10
19+
20+
XCTAssertTrue(b.ends(with: a))
21+
}
22+
23+
func testDoesntEndWithWrongSuffix() {
24+
let a = 8...9
25+
let b = 1...10
26+
27+
XCTAssertFalse(b.ends(with: a))
28+
}
29+
30+
func testDoesntEndWithTooLongSuffix() {
31+
XCTAssertFalse((2...5).ends(with: (1...10)))
32+
}
33+
34+
func testEndsWithEmpty() {
35+
let a = 8...10
36+
let empty = [Int]()
37+
XCTAssertTrue(a.ends(with: empty))
38+
}
39+
40+
func testEmptyEndsWithEmpty() {
41+
let empty = [Int]()
42+
XCTAssertTrue(empty.ends(with: empty))
43+
}
44+
45+
func testEmptyDoesNotEndWithNonempty() {
46+
XCTAssertFalse([].ends(with: 1...10))
47+
}
48+
}
49+
50+
final class EndsWithNonEquatableTests: XCTestCase {
51+
func testEndsWithCorrectSuffix() {
52+
let a = nonEq(8...10)
53+
let b = nonEq(1...10)
54+
55+
XCTAssertTrue(b.ends(with: a, by: areEquivalent))
56+
}
57+
58+
func testDoesntEndWithWrongSuffix() {
59+
let a = nonEq(8...9)
60+
let b = nonEq(1...10)
61+
62+
XCTAssertFalse(b.ends(with: a, by: areEquivalent))
63+
}
64+
65+
func testDoesntEndWithTooLongSuffix() {
66+
XCTAssertFalse(nonEq(2...5).ends(with: nonEq(1...10), by: areEquivalent))
67+
}
68+
69+
func testEndsWithEmpty() {
70+
let a = nonEq(8...10)
71+
let empty = [NotEquatable<Int>]()
72+
XCTAssertTrue(a.ends(with: empty, by: areEquivalent))
73+
}
74+
75+
func testEmptyEndsWithEmpty() {
76+
let empty = [NotEquatable<Int>]()
77+
XCTAssertTrue(empty.ends(with: empty, by: areEquivalent))
78+
}
79+
80+
func testEmptyDoesNotEndWithNonempty() {
81+
XCTAssertFalse([].ends(with: nonEq(1...10), by: areEquivalent))
82+
}
83+
84+
private func nonEq(_ range: ClosedRange<Int>) -> Array<NotEquatable<Int>> {
85+
range.map(NotEquatable.init)
86+
}
87+
88+
private func areEquivalent<T: Equatable>(lhs: NotEquatable<T>, rhs: NotEquatable<T>) -> Bool {
89+
lhs.value == rhs.value
90+
}
91+
92+
private struct NotEquatable<T> {
93+
let value: T
94+
}
95+
}
96+

0 commit comments

Comments
 (0)