Skip to content

Commit e355619

Browse files
committed
Add functions checking overlap of sorted sequences
1 parent 7597e5f commit e355619

File tree

4 files changed

+265
-33
lines changed

4 files changed

+265
-33
lines changed

Guides/Inclusion.md

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
[[Source](https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/Includes.swift) |
44
[Tests](https://github.com/apple/swift-algorithms/blob/main/Tests/SwiftAlgorithmsTests/IncludesTests.swift)]
55

6-
Check if one sequence includes another.
6+
Check if one sequence includes another with the `includes` function.
77
Both sequences must be sorted along the same criteria,
88
which must provide a strict weak ordering.
99

@@ -17,26 +17,66 @@ let thirdIncludesSecond = third.includes(sorted: second, sortedBy: >) // false
1717
let secondIncludesFirst = second.includes(sorted: first, sortedBy: >) // false
1818
```
1919

20+
For a more detailed computation of how much the two sequences intersect,
21+
use the `overlap` function.
22+
23+
```swift
24+
let firstOverlapThird = first.overlap(withSorted: third, sortedBy: >)
25+
assert(firstOverlapThird.elementsFromSelf == .some(true))
26+
assert(firstOverlapThird.sharedElements == .some(true))
27+
assert(firstOverlapThird.elementsFromOther == .some(false))
28+
```
29+
30+
By default, `overlap` returns its result after at least one of the sequences ends.
31+
To immediately end comparisons as soon as an element for a particular category is found,
32+
pass in the appropriate "`bail`" argument.
33+
34+
```swift
35+
let firstOverlapThirdAgain = first.overlap(withSorted: third, bailAfterShared: true, sortedBy: >)
36+
assert(firstOverlapThirdAgain.sharedElements == .some(true))
37+
assert(firstOverlapThirdAgain.elementsFromSelf == .some(true))
38+
assert(firstOverlapThirdAgain.elementsFromOther == .none)
39+
```
40+
41+
When `overlap` ends by a short-circut,
42+
only the flag for the short-circuted category will be `true`.
43+
The returned flags for the other categories may be `nil`.
44+
If multiple categories are flagged for short-circuting,
45+
only the first one found is guaranteed to be `true`.
46+
When no bailing argument is supplied,
47+
none of the returned flags will be `nil`.
48+
2049
If a predicate is not supplied,
2150
then the less-than operator is used,
2251
but only if the `Element` type conforms to `Comparable`.
2352

2453
```swift
2554
(1...3).includes(sorted: 1..<3) // true
55+
(1...3).overlap(sorted: 1..<3).elementsFromOther // .some(false)
2656
```
2757

2858
## Detailed Design
2959

30-
Two new methods are added to `Sequence`:
60+
Four new methods are added to `Sequence`:
3161

3262
```swift
3363
extension Sequence {
34-
public func
64+
@inlinable public func
3565
includes<T>(
3666
sorted other: T,
3767
sortedBy areInIncreasingOrder: (Element, Element) throws -> Bool
3868
) rethrows -> Bool
3969
where T : Sequence, Self.Element == T.Element
70+
71+
public func
72+
overlap<T>(
73+
withSorted other: T,
74+
bailAfterSelfExclusive: Bool = false,
75+
bailAfterShared: Bool = false,
76+
bailAfterOtherExclusive: Bool = false,
77+
sortedBy areInIncreasingOrder: (Element, Element) throws -> Bool
78+
) rethrows -> (elementsFromSelf: Bool?, sharedElements: Bool?, elementsFromOther: Bool?)
79+
where T : Sequence, Self.Element == T.Element
4080
}
4181

4282
extension Sequence where Self.Element : Comparable {
@@ -45,16 +85,26 @@ extension Sequence where Self.Element : Comparable {
4585
sorted other: T
4686
) -> Bool
4787
where T : Sequence, Self.Element == T.Element
88+
89+
@inlinable public func
90+
overlap<T>(
91+
withSorted other: T,
92+
bailAfterSelfExclusive: Bool = false,
93+
bailAfterShared: Bool = false,
94+
bailAfterOtherExclusive: Bool = false
95+
) -> (elementsFromSelf: Bool?, sharedElements: Bool?, elementsFromOther: Bool?)
96+
where T : Sequence, Self.Element == T.Element
4897
}
4998
```
5099

51100
The `Sequence.includes(sorted:)` method calls the
52101
`Sequence.includes(sorted:sortedBy:)` method with the less-than operator for
53102
the latter's second argument.
103+
The same relationship applies to both versions of `Sequence.overlap`.
54104

55105
### Complexity
56106

57-
Calling either method is O(_n_),
107+
Calling any of these methods is O(_n_),
58108
where *n* is the length of the shorter sequence.
59109

60110
### Naming

Sources/Algorithms/Documentation.docc/Inclusion.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Inclusion
22

33
Check if one sorted sequence is completely contained in another.
4+
Can also determine the degree overlap between two sorted sequences.
45
The sort criteria is a user-supplied predicate.
56
The predicate can be omitted to default to the less-than operator.
67

@@ -10,3 +11,5 @@ The predicate can be omitted to default to the less-than operator.
1011

1112
- ``Swift/Sequence/includes(sorted:sortedBy:)``
1213
- ``Swift/Sequence/includes(sorted:)``
14+
- ``Swift/Sequence/overlap(withSorted:bailAfterSelfExclusive:bailAfterShared:bailAfterOtherExclusive:sortedBy:)``
15+
- ``Swift/Sequence/overlap(withSorted:bailAfterSelfExclusive:bailAfterShared:bailAfterOtherExclusive:)``

Sources/Algorithms/Includes.swift

Lines changed: 179 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -35,66 +35,216 @@ extension Sequence {
3535
/// sequence.
3636
///
3737
/// - Complexity: O(*n*), where `n` is the length of the shorter sequence.
38+
@inlinable
3839
public func includes<T: Sequence>(
3940
sorted other: T,
4041
sortedBy areInIncreasingOrder: (Element, Element) throws -> Bool
4142
) rethrows -> Bool
4243
where T.Element == Element {
43-
// Originally, there was a function that evaluated the two sequences'
44-
// elements with respect to having elements exclusive to the receiver,
45-
// elements exclusive to `other`, and shared elements. But this function
46-
// only needs to know when an element that is exclusive to the `other` is
47-
// found. So, that function's guts were ripped out and repurposed.
44+
return try !overlap(withSorted: other, bailAfterOtherExclusive: true,
45+
sortedBy: areInIncreasingOrder).elementsFromOther!
46+
}
47+
}
48+
49+
extension Sequence where Element: Comparable {
50+
/// Assuming that this sequence and the given sequence are sorted,
51+
/// determine whether the given sequence is contained within this one.
52+
///
53+
/// let base = [0, 1, 2, 3, 6, 6, 7, 8, 9]
54+
/// assert(base.includes(sorted: [1, 2, 6, 7, 8]))
55+
/// assert(!base.includes(sorted: [1, 2, 5, 7, 8]))
56+
///
57+
/// The elements of the argument need not be contiguous in the receiver.
58+
///
59+
/// - Precondition: Both the receiver and `other` must be sorted.
60+
/// At least one of the involved sequences must be finite.
61+
///
62+
/// - Parameter other: The sequence that is compared against the receiver.
63+
/// - Returns: Whether the entirety of `other` is contained within this
64+
/// sequence.
65+
///
66+
/// - Complexity: O(*n*), where `n` is the length of the shorter sequence.
67+
@inlinable
68+
public func includes<T: Sequence>(sorted other: T) -> Bool
69+
where T.Element == Element {
70+
return includes(sorted: other, sortedBy: <)
71+
}
72+
}
73+
74+
//===----------------------------------------------------------------------===//
75+
// MARK: - Sequence.overlap(withSorted:sortedBy:)
76+
//-------------------------------------------------------------------------===//
77+
78+
extension Sequence {
79+
/// Assuming that this sequence and the given sequence are sorted according
80+
/// to the given predicate, check if the sequences have overlap and/or
81+
/// exclusive elements.
82+
///
83+
/// let base = [9, 8, 7, 6, 6, 3, 2, 1, 0]
84+
/// let test1 = base.overlap(withSorted: [8, 7, 6, 2, 1], sortedBy: >)
85+
/// let test2 = base.overlap(withSorted: [8, 7, 5, 2, 1], sortedBy: >)
86+
/// assert(test1.elementsFromSelf!)
87+
/// assert(test1.sharedElements!)
88+
/// assert(!test1.elementsFromOther!)
89+
/// assert(test2.elementsFromSelf!)
90+
/// assert(test2.sharedElements!)
91+
/// assert(test2.elementsFromOther!)
92+
///
93+
/// - Precondition: Both the receiver and `other` must be sorted according to
94+
/// `areInIncreasingOrder`,
95+
/// which must be a strict weak ordering over its arguments.
96+
/// Either the receiver, `other`, or both must be finite.
97+
///
98+
/// - Parameters:
99+
/// - other: The sequence that is compared against the receiver.
100+
/// - areInIncreasingOrder: The sorting criteria.
101+
/// - bailAfterSelfExclusive: Indicate that this function should abort as
102+
/// soon as one element that is exclusive to this sequence is found.
103+
/// If not given, defaults to `false`.
104+
/// - bailAfterShared: Indicate that this function should abort as soon as
105+
/// an element that both sequences share is found.
106+
/// If not given, defaults to `false`.
107+
/// - bailAfterOtherExclusive: Indicate that this function should abort as
108+
/// soon as one element that is exclusive to `other` is found.
109+
/// If not given, defaults to `false`.
110+
/// - Returns: A tuple of three `Bool` members indicating whether there are
111+
/// elements exclusive to `self`,
112+
/// there are elements shared between the sequences,
113+
/// and there are elements exclusive to `other`.
114+
/// If a member is `true`,
115+
/// then at least one element in that category exists.
116+
/// If a member is `false`,
117+
/// then there are no elements in that category.
118+
/// If a member is `nil`,
119+
/// then the function aborted before its category's status could be
120+
/// determined.
121+
///
122+
/// - Complexity: O(*n*), where `n` is the length of the shorter sequence.
123+
public func overlap<T: Sequence>(
124+
withSorted other: T,
125+
bailAfterSelfExclusive: Bool = false,
126+
bailAfterShared: Bool = false,
127+
bailAfterOtherExclusive: Bool = false,
128+
sortedBy areInIncreasingOrder: (Element, Element) throws -> Bool
129+
) rethrows -> (
130+
elementsFromSelf: Bool?,
131+
sharedElements: Bool?,
132+
elementsFromOther: Bool?
133+
)
134+
where T.Element == Element {
48135
var firstElement, secondElement: Element?
49136
var iterator = makeIterator(), otherIterator = other.makeIterator()
50-
while true {
137+
var result: (fromSelf: Bool?, shared: Bool?, fromOther: Bool?)
138+
loop:
139+
while result != (true, true, true) {
51140
firstElement = firstElement ?? iterator.next()
52141
secondElement = secondElement ?? otherIterator.next()
53142
switch (firstElement, secondElement) {
54-
case let (first?, second?) where try areInIncreasingOrder(first, second):
55-
// Found an element exclusive to `self`, move on.
143+
case let (s?, o?) where try areInIncreasingOrder(s, o):
144+
// Exclusive to self
145+
result.fromSelf = true
146+
guard !bailAfterSelfExclusive else { break loop }
147+
148+
// Move to the next element in self.
56149
firstElement = nil
57-
case let (first?, second?) where try areInIncreasingOrder(second, first):
58-
// Found an element exclusive to `other`.
59-
return false
150+
case let (s?, o?) where try areInIncreasingOrder(o, s):
151+
// Exclusive to other
152+
result.fromOther = true
153+
guard !bailAfterOtherExclusive else { break loop }
154+
155+
// Move to the next element in other.
156+
secondElement = nil
60157
case (_?, _?):
61-
// Found a shared element, move on.
158+
// Shared
159+
result.shared = true
160+
guard !bailAfterShared else { break loop }
161+
162+
// Iterate to the next element for both sequences.
62163
firstElement = nil
63164
secondElement = nil
165+
case (_?, nil):
166+
// Never bail, just finalize after finding an exclusive to self.
167+
result.fromSelf = true
168+
result.shared = result.shared ?? false
169+
result.fromOther = result.fromOther ?? false
170+
break loop
64171
case (nil, _?):
65-
// Found an element exclusive to `other`, and any remaining elements
66-
// will be exclusive to `other`.
67-
return false
68-
default:
69-
// The elements from `other` (and possibly `self` too) have been
70-
// exhausted without disproving inclusion.
71-
return true
172+
// Never bail, just finalize after finding an exclusive to other.
173+
result.fromSelf = result.fromSelf ?? false
174+
result.shared = result.shared ?? false
175+
result.fromOther = true
176+
break loop
177+
case (nil, nil):
178+
// Finalize everything instead of bailing
179+
result.fromSelf = result.fromSelf ?? false
180+
result.shared = result.shared ?? false
181+
result.fromOther = result.fromOther ?? false
182+
break loop
72183
}
73184
}
185+
return (result.fromSelf, result.shared, result.fromOther)
74186
}
75187
}
76188

77189
extension Sequence where Element: Comparable {
78190
/// Assuming that this sequence and the given sequence are sorted,
79-
/// determine whether the given sequence is contained within this one.
191+
/// check if the sequences have overlap and/or exclusive elements.
80192
///
81193
/// let base = [0, 1, 2, 3, 6, 6, 7, 8, 9]
82-
/// assert(base.includes(sorted: [1, 2, 6, 7, 8]))
83-
/// assert(!base.includes(sorted: [1, 2, 5, 7, 8]))
84-
///
85-
/// The elements of the argument need not be contiguous in the receiver.
194+
/// let test1 = base.overlap(withSorted: [1, 2, 6, 7, 8])
195+
/// let test2 = base.overlap(withSorted: [1, 2, 5, 7, 8])
196+
/// assert(test1.elementsFromSelf!)
197+
/// assert(test1.sharedElements!)
198+
/// assert(!test1.elementsFromOther!)
199+
/// assert(test2.elementsFromSelf!)
200+
/// assert(test2.sharedElements!)
201+
/// assert(test2.elementsFromOther!)
86202
///
87203
/// - Precondition: Both the receiver and `other` must be sorted.
88204
/// At least one of the involved sequences must be finite.
89205
///
90-
/// - Parameter other: The sequence that is compared against the receiver.
91-
/// - Returns: Whether the entirety of `other` is contained within this
92-
/// sequence.
206+
/// - Parameters:
207+
/// - other: The sequence that is compared against the receiver.
208+
/// - bailAfterSelfExclusive: Indicate that this function should abort as
209+
/// soon as one element that is exclusive to this sequence is found.
210+
/// If not given, defaults to `false`.
211+
/// - bailAfterShared: Indicate that this function should abort as soon as
212+
/// an element that both sequences share is found.
213+
/// If not given, defaults to `false`.
214+
/// - bailAfterOtherExclusive: Indicate that this function should abort as
215+
/// soon as one element that is exclusive to `other` is found.
216+
/// If not given, defaults to `false`.
217+
/// - Returns: A tuple of three `Bool` members indicating whether there are
218+
/// elements exclusive to `self`,
219+
/// elements shared between the sequences,
220+
/// and elements exclusive to `other`.
221+
/// If a member is `true`,
222+
/// then at least one element in that category exists.
223+
/// If a member is `false`,
224+
/// then there are no elements in that category.
225+
/// If a member is `nil`,
226+
/// then the function aborted before its category's status could be
227+
/// determined.
93228
///
94229
/// - Complexity: O(*n*), where `n` is the length of the shorter sequence.
95230
@inlinable
96-
public func includes<T: Sequence>(sorted other: T) -> Bool
231+
public func overlap<T: Sequence>(
232+
withSorted other: T,
233+
bailAfterSelfExclusive: Bool = false,
234+
bailAfterShared: Bool = false,
235+
bailAfterOtherExclusive: Bool = false
236+
) -> (
237+
elementsFromSelf: Bool?,
238+
sharedElements: Bool?,
239+
elementsFromOther: Bool?
240+
)
97241
where T.Element == Element {
98-
return includes(sorted: other, sortedBy: <)
242+
return overlap(
243+
withSorted: other,
244+
bailAfterSelfExclusive: bailAfterSelfExclusive,
245+
bailAfterShared: bailAfterShared,
246+
bailAfterOtherExclusive: bailAfterOtherExclusive,
247+
sortedBy: <
248+
)
99249
}
100250
}

Tests/SwiftAlgorithmsTests/IncludesTests.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,33 @@ final class IncludesTests: XCTestCase {
7171
XCTAssertTrue(base.includes(sorted: [1, 2, 6, 7, 8]))
7272
XCTAssertFalse(base.includes(sorted: [1, 2, 5, 7, 8]))
7373
}
74+
75+
/// Confirm the example code from `Sequence.overlap(withSorted:`
76+
/// `bailAfterSelfExclusive:bailAfterShared:bailAfterOtherExclusive:`
77+
/// `sortedBy:)`.
78+
func testThirdDiscussionCode() {
79+
let base = [9, 8, 7, 6, 6, 3, 2, 1, 0]
80+
let test1 = base.overlap(withSorted: [8, 7, 6, 2, 1], sortedBy: >)
81+
let test2 = base.overlap(withSorted: [8, 7, 5, 2, 1], sortedBy: >)
82+
XCTAssertTrue(test1.elementsFromSelf!)
83+
XCTAssertTrue(test1.sharedElements!)
84+
XCTAssertFalse(test1.elementsFromOther!)
85+
XCTAssertTrue(test2.elementsFromSelf!)
86+
XCTAssertTrue(test2.sharedElements!)
87+
XCTAssertTrue(test2.elementsFromOther!)
88+
}
89+
90+
/// Confirm the example code from `Sequence.overlap(withSorted:`
91+
/// `bailAfterSelfExclusive:bailAfterShared:bailAfterOtherExclusive:)`.
92+
func testFourthDiscussionCode() {
93+
let base = [0, 1, 2, 3, 6, 6, 7, 8, 9]
94+
let test1 = base.overlap(withSorted: [1, 2, 6, 7, 8])
95+
let test2 = base.overlap(withSorted: [1, 2, 5, 7, 8])
96+
XCTAssertTrue(test1.elementsFromSelf!)
97+
XCTAssertTrue(test1.sharedElements!)
98+
XCTAssertFalse(test1.elementsFromOther!)
99+
XCTAssertTrue(test2.elementsFromSelf!)
100+
XCTAssertTrue(test2.sharedElements!)
101+
XCTAssertTrue(test2.elementsFromOther!)
102+
}
74103
}

0 commit comments

Comments
 (0)