Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Detection of One Sorted Sequence Being a Subset of Another #234

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
150 changes: 150 additions & 0 deletions Guides/Inclusion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# Inclusion

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

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

```swift
let first = [4, 3, 2, 1], second = [3, 2, 1, 0], third = [3, 2]
let firstIncludesSecond = first.includes(sorted: second, sortedBy: >) // false
let secondIncludesThird = second.includes(sorted: third, sortedBy: >) // true
let thirdIncludesFirst = third.includes(sorted: first, sortedBy: >) // false
let firstIncludesThird = first.includes(sorted: third, sortedBy: >) // true
let thirdIncludesSecond = third.includes(sorted: second, sortedBy: >) // false
let secondIncludesFirst = second.includes(sorted: first, sortedBy: >) // false
```

For a more detailed computation of how much the two sequences intersect,
use the `overlap` function.

```swift
let firstOverlapThird = first.overlap(withSorted: third, sortedBy: >)
assert(firstOverlapThird.hasElementsExclusiveToFirst)
assert(firstOverlapThird.hasSharedElements)
assert(!firstOverlapThird.hasElementsExclusiveToSecond)
```

By default, `overlap` returns its result after at least one of the sequences ends.
To immediately end comparisons as soon as an element for a particular part is found,
pass in the appropriate flags for the optional stopping-point argument.

```swift
let firstOverlapThirdAgain = first.overlap(withSorted: third, stoppingFor: .anythingShared, sortedBy: >)
assert(firstOverlapThirdAgain.hasSharedElements)
```

When `overlap` ends by a short-circuit,
exactly one of the stopping-condition flags will be set to `true`.
To avoid checking all the element category properties,
apply the overlap's' `canSatisfy(:)` function on the stopping conditions to
check if a short-circuit happened.

```swift
assert(firstOverlapThirdAgain.canSatisfy(.anythingShared))
```

If a predicate is not supplied,
then the less-than operator is used,
but only if the `Element` type conforms to `Comparable`.

```swift
(1...3).includes(sorted: 1..<3) // true
(1...3).overlap(sorted: 1..<3).hasElementsExclusiveToSecond // false
```

## Detailed Design

Four new methods are added to `Sequence`:

```swift
extension Sequence {
@inlinable public func
includes<T>(
sorted other: T,
sortedBy areInIncreasingOrder: (Element, Element) throws -> Bool
) rethrows -> Bool
where T : Sequence, Self.Element == T.Element

public func
overlap<T>(
withSorted other: T,
stoppingFor condition: OverlapHaltCondition = .nothing,
sortedBy areInIncreasingOrder: (Element, Element) throws -> Bool
) rethrows -> OverlapDegree
where T : Sequence, Self.Element == T.Element
}

extension Sequence where Self.Element : Comparable {
@inlinable public func
includes<T>(
sorted other: T
) -> Bool
where T : Sequence, Self.Element == T.Element

@inlinable public func
overlap<T>(
withSorted other: T,
stoppingFor condition: OverlapHaltCondition = .nothing
) -> OverlapDegree
where T : Sequence, Self.Element == T.Element
}
```

And two types:

```swift
public enum OverlapDegree : UInt, CaseIterable {
case bothEmpty, onlyFirstNonempty, onlySecondNonempty, disjoint,identical,
firstIncludesNonemptySecond, secondIncludesNonemptyFirst, partialOverlap
}

extension OverlapDegree {
@inlinable public var hasElementsExclusiveToFirst: Bool { get }
@inlinable public var hasElementsExclusiveToSecond: Bool { get }
@inlinable public var hasSharedElements: Bool { get }
}

extension OverlapDegree {
@inlinable public func canSatisfy(_ condition: OverlapHaltCondition) -> Bool
}

public enum OverlapHaltCondition : UInt, CaseIterable {
case nothing, anyExclusiveToFirst, anyExclusiveToSecond, anyExclusive,
anythingShared, anyFromFirst, anyFromSecond, anything
}

extension OverlapHaltCondition {
@inlinable public var stopsOnElementsExclusiveToFirst: Bool { get }
@inlinable public var stopsOnElementsExclusiveToSecond: Bool { get }
@inlinable public var stopsOnSharedElements: Bool { get }
}
```

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

### Complexity

Calling any of these methods is O(_n_),
where *n* is the length of the shorter sequence.

### Naming

These methods' base name is inspired by the C++ function `std::includes`.

### Comparison with Other Languages

**[C++][C++]:** Has an `includes` function family.

**[Haskell][Haskell]:** Has the `isInfixOf` function, plus the `isPrefixOf`,
`isSuffixOf`, and `isSubsequenceOf` functions.

<!-- Link references for other languages -->

[C++]: https://en.cppreference.com/w/cpp/algorithm/includes
[Haskell]: https://hackage.haskell.org/package/base-4.20.0.1/docs/Data-List.html#v:isInfixOf
1 change: 1 addition & 0 deletions Sources/Algorithms/Documentation.docc/Algorithms.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ Explore more chunking methods and the remainder of the Algorithms package, group
- <doc:Filtering>
- <doc:Reductions>
- <doc:Partitioning>
- <doc:Inclusion>
20 changes: 20 additions & 0 deletions Sources/Algorithms/Documentation.docc/Inclusion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Inclusion

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

## Topics

### Inclusion

- ``Swift/Sequence/includes(sorted:sortedBy:)``
- ``Swift/Sequence/includes(sorted:)``
- ``Swift/Sequence/overlap(withSorted:stoppingFor:sortedBy:)``
- ``Swift/Sequence/overlap(withSorted:stoppingFor:)``

### Supporting Types

- ``OverlapDegree``
- ``OverlapHaltCondition``
Loading