Skip to content

Commit ba38c7a

Browse files
committed
Change the return for the overlap functions to a custom type
1 parent e355619 commit ba38c7a

File tree

4 files changed

+177
-87
lines changed

4 files changed

+177
-87
lines changed

Guides/Inclusion.md

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ use the `overlap` function.
2222

2323
```swift
2424
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))
25+
assert(firstOverlapThird.hasElementsExclusiveToFirst)
26+
assert(firstOverlapThird.hasSharedElements)
27+
assert(!firstOverlapThird.hasElementsExclusiveToSecond)
2828
```
2929

3030
By default, `overlap` returns its result after at least one of the sequences ends.
@@ -33,18 +33,16 @@ pass in the appropriate "`bail`" argument.
3333

3434
```swift
3535
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)
36+
assert(firstOverlapThirdAgain.hasSharedElements)
37+
// The other two flags may have random values.
3938
```
4039

4140
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`.
41+
exactly one of the short-circut categories in the argument list will have its
42+
flag in the return value set to `true`.
43+
Otherwise all of the short-circut categories will have their flags as `false`.
44+
Only in the latter case are the flags for categories not short-circuted would
45+
be accurate.
4846

4947
If a predicate is not supplied,
5048
then the less-than operator is used,
@@ -97,6 +95,21 @@ extension Sequence where Self.Element : Comparable {
9795
}
9896
```
9997

98+
And one type:
99+
100+
```swift
101+
public enum OverlapDegree : UInt, CaseIterable {
102+
case bothEmpty, onlyFirstNonempty, onlySecondNonempty, disjoint,identical,
103+
firstIncludesNonemptySecond, secondIncludesNonemptyFirst, partialOverlap
104+
}
105+
106+
extension OverlapDegree {
107+
@inlinable public var hasElementsExclusiveToFirst: Bool { get }
108+
@inlinable public var hasElementsExclusiveToSecond: Bool { get }
109+
@inlinable public var hasSharedElements: Bool { get }
110+
}
111+
```
112+
100113
The `Sequence.includes(sorted:)` method calls the
101114
`Sequence.includes(sorted:sortedBy:)` method with the less-than operator for
102115
the latter's second argument.

Sources/Algorithms/Documentation.docc/Inclusion.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +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.
4+
Can also determine the degree of overlap between two sorted sequences.
55
The sort criteria is a user-supplied predicate.
66
The predicate can be omitted to default to the less-than operator.
77

@@ -13,3 +13,7 @@ The predicate can be omitted to default to the less-than operator.
1313
- ``Swift/Sequence/includes(sorted:)``
1414
- ``Swift/Sequence/overlap(withSorted:bailAfterSelfExclusive:bailAfterShared:bailAfterOtherExclusive:sortedBy:)``
1515
- ``Swift/Sequence/overlap(withSorted:bailAfterSelfExclusive:bailAfterShared:bailAfterOtherExclusive:)``
16+
17+
### Supporting Types
18+
19+
- ``OverlapDegree``

Sources/Algorithms/Includes.swift

Lines changed: 110 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,60 @@
1010
//===----------------------------------------------------------------------===//
1111

1212
//===----------------------------------------------------------------------===//
13-
// MARK: Sequence.includes(sorted:sortedBy:)
13+
// MARK: OverlapDegree
14+
//-------------------------------------------------------------------------===//
15+
16+
/// The amount of overlap between two sets.
17+
public enum OverlapDegree: UInt, CaseIterable {
18+
/// Both sets are empty (degenerate).
19+
case bothEmpty
20+
/// Have a nonempty first set, empty second (degenerate).
21+
case onlyFirstNonempty
22+
/// Have an empty first set, nonempty second (degenerate).
23+
case onlySecondNonempty
24+
/// Have two nonempty sets with no overlap.
25+
case disjoint
26+
/// The two sets are equivalent and nonempty.
27+
case identical
28+
/// The first set is a strict superset of a nonempty second.
29+
case firstIncludesNonemptySecond
30+
/// The first set is a nonempty strict subset of the second.
31+
case secondIncludesNonemptyFirst
32+
/// The sets overlap but each still have exclusive elements.
33+
case partialOverlap
34+
}
35+
36+
extension OverlapDegree {
37+
/// The bit mask checking if there are elements exclusive to the first set.
38+
@usableFromInline
39+
static var firstOnlyMask: RawValue { 1 << 0 }
40+
/// The bit mask checking if there are elements exclusive to the second set.
41+
@usableFromInline
42+
static var secondOnlyMask: RawValue { 1 << 1 }
43+
/// The bit mask checking if there are elements shared by both sets.
44+
@usableFromInline
45+
static var sharedMask: RawValue { 1 << 2 }
46+
}
47+
48+
extension OverlapDegree {
49+
/// Whether there are any elements in the first set that are not in
50+
/// the second.
51+
@inlinable
52+
public var hasElementsExclusiveToFirst: Bool
53+
{ rawValue & Self.firstOnlyMask != 0 }
54+
/// Whether there are any elements in the second set that are not in
55+
/// the first.
56+
@inlinable
57+
public var hasElementsExclusiveToSecond: Bool
58+
{ rawValue & Self.secondOnlyMask != 0 }
59+
/// Whether there are any elements that occur in both sets.
60+
@inlinable
61+
public var hasSharedElements: Bool
62+
{ rawValue & Self.sharedMask != 0 }
63+
}
64+
65+
//===----------------------------------------------------------------------===//
66+
// MARK: - Sequence.includes(sorted:sortedBy:)
1467
//-------------------------------------------------------------------------===//
1568

1669
extension Sequence {
@@ -41,8 +94,11 @@ extension Sequence {
4194
sortedBy areInIncreasingOrder: (Element, Element) throws -> Bool
4295
) rethrows -> Bool
4396
where T.Element == Element {
44-
return try !overlap(withSorted: other, bailAfterOtherExclusive: true,
45-
sortedBy: areInIncreasingOrder).elementsFromOther!
97+
return try !overlap(
98+
withSorted: other,
99+
bailAfterOtherExclusive: true,
100+
sortedBy: areInIncreasingOrder
101+
).hasElementsExclusiveToSecond
46102
}
47103
}
48104

@@ -83,12 +139,12 @@ extension Sequence {
83139
/// let base = [9, 8, 7, 6, 6, 3, 2, 1, 0]
84140
/// let test1 = base.overlap(withSorted: [8, 7, 6, 2, 1], sortedBy: >)
85141
/// 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!)
142+
/// assert(test1.hasElementsExclusiveToFirst)
143+
/// assert(test1.hasSharedElements)
144+
/// assert(!test1.hasElementsExclusiveToSecond)
145+
/// assert(test2.hasElementsExclusiveToFirst!)
146+
/// assert(test2.hasSharedElements)
147+
/// assert(test2.hasElementsExclusiveToSecond)
92148
///
93149
/// - Precondition: Both the receiver and `other` must be sorted according to
94150
/// `areInIncreasingOrder`,
@@ -107,17 +163,15 @@ extension Sequence {
107163
/// - bailAfterOtherExclusive: Indicate that this function should abort as
108164
/// soon as one element that is exclusive to `other` is found.
109165
/// 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.
166+
/// - Returns: A value representing the categories of overlap found.
167+
/// If none of the abort arguments were `true`,
168+
/// or otherwise none of their corresponding categories were found,
169+
/// then all of the category flags from the returned value are accurate.
170+
/// Otherwise,
171+
/// the returned value has exactly one of the flags in the
172+
/// short-circuit subset as `true`,
173+
/// and the flags outside that set may have invalid values.
174+
/// The receiver is considered the first set, and `other` as the second.
121175
///
122176
/// - Complexity: O(*n*), where `n` is the length of the shorter sequence.
123177
public func overlap<T: Sequence>(
@@ -126,63 +180,63 @@ extension Sequence {
126180
bailAfterShared: Bool = false,
127181
bailAfterOtherExclusive: Bool = false,
128182
sortedBy areInIncreasingOrder: (Element, Element) throws -> Bool
129-
) rethrows -> (
130-
elementsFromSelf: Bool?,
131-
sharedElements: Bool?,
132-
elementsFromOther: Bool?
133-
)
183+
) rethrows -> OverlapDegree
134184
where T.Element == Element {
135185
var firstElement, secondElement: Element?
136186
var iterator = makeIterator(), otherIterator = other.makeIterator()
137-
var result: (fromSelf: Bool?, shared: Bool?, fromOther: Bool?)
187+
var fromSelf, shared, fromOther: Bool?
138188
loop:
139-
while result != (true, true, true) {
189+
while (fromSelf, shared, fromOther) != (true, true, true) {
140190
firstElement = firstElement ?? iterator.next()
141191
secondElement = secondElement ?? otherIterator.next()
142192
switch (firstElement, secondElement) {
143193
case let (s?, o?) where try areInIncreasingOrder(s, o):
144194
// Exclusive to self
145-
result.fromSelf = true
195+
fromSelf = true
146196
guard !bailAfterSelfExclusive else { break loop }
147197

148198
// Move to the next element in self.
149199
firstElement = nil
150200
case let (s?, o?) where try areInIncreasingOrder(o, s):
151201
// Exclusive to other
152-
result.fromOther = true
202+
fromOther = true
153203
guard !bailAfterOtherExclusive else { break loop }
154204

155205
// Move to the next element in other.
156206
secondElement = nil
157207
case (_?, _?):
158208
// Shared
159-
result.shared = true
209+
shared = true
160210
guard !bailAfterShared else { break loop }
161211

162212
// Iterate to the next element for both sequences.
163213
firstElement = nil
164214
secondElement = nil
165215
case (_?, nil):
166216
// 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
217+
fromSelf = true
218+
shared = shared ?? false
219+
fromOther = fromOther ?? false
170220
break loop
171221
case (nil, _?):
172222
// 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
223+
fromSelf = fromSelf ?? false
224+
shared = shared ?? false
225+
fromOther = true
176226
break loop
177227
case (nil, nil):
178228
// Finalize everything instead of bailing
179-
result.fromSelf = result.fromSelf ?? false
180-
result.shared = result.shared ?? false
181-
result.fromOther = result.fromOther ?? false
229+
fromSelf = fromSelf ?? false
230+
shared = shared ?? false
231+
fromOther = fromOther ?? false
182232
break loop
183233
}
184234
}
185-
return (result.fromSelf, result.shared, result.fromOther)
235+
236+
let selfBit = fromSelf == true ? OverlapDegree.firstOnlyMask : 0,
237+
shareBit = shared == true ? OverlapDegree.sharedMask : 0,
238+
otherBit = fromOther == true ? OverlapDegree.secondOnlyMask : 0
239+
return .init(rawValue: selfBit | shareBit | otherBit)!
186240
}
187241
}
188242

@@ -193,12 +247,12 @@ extension Sequence where Element: Comparable {
193247
/// let base = [0, 1, 2, 3, 6, 6, 7, 8, 9]
194248
/// let test1 = base.overlap(withSorted: [1, 2, 6, 7, 8])
195249
/// 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!)
250+
/// assert(test1.hasElementsExclusiveToFirst)
251+
/// assert(test1.hasSharedElements)
252+
/// assert(!test1.hasElementsExclusiveToSecond)
253+
/// assert(test2.hasElementsExclusiveToFirst)
254+
/// assert(test2.hasSharedElements)
255+
/// assert(test2.hasElementsExclusiveToSecond)
202256
///
203257
/// - Precondition: Both the receiver and `other` must be sorted.
204258
/// At least one of the involved sequences must be finite.
@@ -214,17 +268,15 @@ extension Sequence where Element: Comparable {
214268
/// - bailAfterOtherExclusive: Indicate that this function should abort as
215269
/// soon as one element that is exclusive to `other` is found.
216270
/// 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.
271+
/// - Returns: A value representing the categories of overlap found.
272+
/// If none of the abort arguments were `true`,
273+
/// or otherwise none of their corresponding categories were found,
274+
/// then all of the category flags from the returned value are accurate.
275+
/// Otherwise,
276+
/// the returned value has exactly one of the flags in the
277+
/// short-circuit subset as `true`,
278+
/// and the flags outside that set may have invalid values.
279+
/// The receiver is considered the first set, and `other` as the second.
228280
///
229281
/// - Complexity: O(*n*), where `n` is the length of the shorter sequence.
230282
@inlinable
@@ -233,11 +285,7 @@ extension Sequence where Element: Comparable {
233285
bailAfterSelfExclusive: Bool = false,
234286
bailAfterShared: Bool = false,
235287
bailAfterOtherExclusive: Bool = false
236-
) -> (
237-
elementsFromSelf: Bool?,
238-
sharedElements: Bool?,
239-
elementsFromOther: Bool?
240-
)
288+
) -> OverlapDegree
241289
where T.Element == Element {
242290
return overlap(
243291
withSorted: other,

0 commit comments

Comments
 (0)