From a83b36ec6005ac733adeae8a5400c9fedd30675f Mon Sep 17 00:00:00 2001 From: Daryle Walker Date: Wed, 28 Oct 2020 04:34:11 -0400 Subject: [PATCH 1/9] Add method to check sequence overlap Add method to sequences that takes another sequence and a comparison predicate, assumes both sequences are sorted along that predicate, and reports how much the sequences overlap in element values. Add an overload that defaults the comparison to the standard less-than operator. Add a type to express the form of set overlap, making optional distinction to degenerate sources. --- CHANGELOG.md | 6 +- Guides/SortedInclusion.md | 66 +++++++++ README.md | 4 + Sources/Algorithms/SortedInclusion.swift | 134 ++++++++++++++++++ .../SortedInclusionTests.swift | 105 ++++++++++++++ 5 files changed, 314 insertions(+), 1 deletion(-) create mode 100644 Guides/SortedInclusion.md create mode 100644 Sources/Algorithms/SortedInclusion.swift create mode 100644 Tests/SwiftAlgorithmsTests/SortedInclusionTests.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index 84f0a457..6dbb5851 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,11 @@ package updates, you can specify your package dependency using ## [Unreleased] -*No changes yet.* +### Additions + +- The `sortedOverlap(with: by:)` and `sortedOverlap(with:)` methods have been + added. They report how much overlap two sorted sequences have, expressed by + the new `Inclusion` type. --- diff --git a/Guides/SortedInclusion.md b/Guides/SortedInclusion.md new file mode 100644 index 00000000..29b6bda4 --- /dev/null +++ b/Guides/SortedInclusion.md @@ -0,0 +1,66 @@ +# Sorted Sequence Inclusion + +[[Source](../Sources/Algorithms/SortedInclusion.swift) | + [Tests](../Tests/SwiftAlgorithmsTests/SortedInclusionTests.swift)] + +Methods to find how much two sorted sequences overlap. + +(To-do: expand on this.) + +## Detailed Design + +The inclusion-detection methods are declared as extensions to `Sequence`. The +overload that defaults comparisons to the standard less-than operator is +constrained to when the `Element` type conforms to `Comparable`. + +A reported inclusion state is expressed with the `Inclusion` type. This state +is based on the existence of elements that are shared, exclusive to the first +sequence, and exclusive to the second sequence. This includes all the +degenerate combinations. Convenience properties are included for easy tests. + +```swift +enum Inclusion { + case bothUninhabited, onlyFirstInhabited, onlySecondInhabited, + dualExclusivesOnly, sharedOnly, firstExtendsSecond, + secondExtendsFirst, dualExclusivesAndShared +} + +extension Inclusion { + var hasExclusivesToFirst: Bool { get } + var hasExclusivesToSecond: Bool { get } + var hasSharedElements: Bool { get } + var areIdentical: Bool { get } + var doesFirstIncludeSecond: Bool { get } + var doesSecondIncludeFirst: Bool { get } +} + +extension Sequence { + func sortedOverlap( + with other: S, + by areInIncreasingOrder: (Element, Element) throws -> Bool + ) rethrows -> Inclusion where S.Element == Element +} + +extension Sequence where Element: Comparable { + func sortedOverlap( + with other: S + ) -> Inclusion where S.Element == Element +} +``` + +### Complexity + +All of these methods have to walk the entirety of both sources, so they work in +O(_n_) operations, where _n_ is the length of the shorter source. + +### Comparison with other languages + +**C++:** The `` library defines the `includes` function, whose +functionality is part of the semantics of `sortedOverlap`. The `includes` +function only detects of the second sequence is included within the first; it +doesn't notify if the inclusion is degenerate, or if inclusion fails because +it's actually reversed, both of which `sortedOverlap` can do. To get the +direct functionality of `includes`, check the `doesFirstIncludeSecond` property +of the return value from `sortedOverlap`. + +(To-do: add other languages.) diff --git a/README.md b/README.md index f73b351c..af2b9fb0 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,10 @@ Read more about the package, and the intent behind it, in the [announcement on s - [`randomStableSample(count:)`, `randomStableSample(count:using:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/RandomSampling.md): Randomly selects a specific number of elements from a collection, preserving their original relative order. - [`uniqued()`, `uniqued(on:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Unique.md): The unique elements of a collection, preserving their order. +#### Comparisons + +-[`sortedOverlap(with: by:)`, `sortedOverlap(with:)`](./Guides/SortedInclusion.md): Reports the degree two sorted sequences overlap. + #### Other useful operations - [`chunked(by:)`, `chunked(on:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Chunked.md): Eager and lazy operations that break a collection into chunks based on either a binary predicate or when the result of a projection changes. diff --git a/Sources/Algorithms/SortedInclusion.swift b/Sources/Algorithms/SortedInclusion.swift new file mode 100644 index 00000000..bd30e0c0 --- /dev/null +++ b/Sources/Algorithms/SortedInclusion.swift @@ -0,0 +1,134 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Algorithms open source project +// +// Copyright (c) 2020 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 +// +//===----------------------------------------------------------------------===// + +/// The manner two (multi-)sets may overlap, including degenerate cases. +public enum Inclusion: UInt, CaseIterable { + /// Neither source had any elements. + case bothUninhabited + /// Only the first source had any elements. + case onlyFirstInhabited + /// Only the second source had any elements. + case onlySecondInhabited + /// Each source has its own elements, without any shared. + case dualExclusivesOnly + /// Each source has elements, all of them shared. + case sharedOnly + /// The second source has elements, but the first has those and some more. + case firstExtendsSecond + /// The first source has elements, but the second has those and some more. + case secondExtendsFirst + /// Each source has exclusive elements, and there are some shared ones. + case dualExclusivesAndShared +} + +extension Inclusion { + /// Whether there are elements exclusive to the first source. + @inlinable public var hasExclusivesToFirst: Bool { rawValue & 0x01 != 0 } + /// Whether there are elements exclusive to the second source. + @inlinable public var hasExclusivesToSecond: Bool { rawValue & 0x02 != 0 } + /// Whether there are elements shared by both sources. + @inlinable public var hasSharedElements: Bool { rawValue & 0x04 != 0 } + + /// Whether the sources are identical. + @inlinable public var areIdentical: Bool { rawValue & 0x03 == 0 } + /// Whether the first source contains everything from the second. + @inlinable public var doesFirstIncludeSecond: Bool { !hasExclusivesToSecond } + /// Whether the second source contains everything from the first. + @inlinable public var doesSecondIncludeFirst: Bool { !hasExclusivesToFirst } +} + +//===----------------------------------------------------------------------===// +// sortedOverlap(with: by:) +//===----------------------------------------------------------------------===// + +extension Sequence { + /// Returns how this sequence and the given sequence overlap, assuming both + /// are sorted according to the given predicate that can compare elements. + /// + /// The predicate must be a *strict weak ordering* over the elements. That + /// is, for any elements `a`, `b`, and `c`, the following conditions must + /// hold: + /// + /// - `areInIncreasingOrder(a, a)` is always `false`. (Irreflexivity) + /// - If `areInIncreasingOrder(a, b)` and `areInIncreasingOrder(b, c)` are + /// both `true`, then `areInIncreasingOrder(a, c)` is also + /// `true`. (Transitive comparability) + /// - Two elements are *incomparable* if neither is ordered before the other + /// according to the predicate. If `a` and `b` are incomparable, and `b` + /// and `c` are incomparable, then `a` and `c` are also incomparable. + /// (Transitive incomparability) + /// + /// - Parameters: + /// - other: A sequence to compare to this sequence. + /// - areInIncreasingOrder: A predicate that returns `true` if its first + /// argument should be ordered before its second argument; otherwise, + /// `false`. + /// - Returns: The degree of inclusion between the sequences. + /// + /// - Complexity: O(*m*), where *m* is the lesser of the length of the + /// sequence and the length of `other`. + public func sortedOverlap( + with other: S, + by areInIncreasingOrder: (Element, Element) throws -> Bool + ) rethrows -> Inclusion where S.Element == Element { + var rawResult: UInt = 0, cache, otherCache: Element?, isDone = false + var iterator = makeIterator(), otherIterator = other.makeIterator() + while !isDone { + cache = cache ?? iterator.next() + otherCache = otherCache ?? otherIterator.next() + switch (cache, otherCache) { + case (nil, nil): + isDone = true + case (_?, nil): + rawResult |= 0x01 + isDone = true + case (nil, _?): + rawResult |= 0x02 + isDone = true + case let (first?, second?): + if try areInIncreasingOrder(first, second) { + rawResult |= 0x01 + cache = nil + } else if try areInIncreasingOrder(second, first) { + rawResult |= 0x02 + otherCache = nil + } else { + rawResult |= 0x04 + cache = nil + otherCache = nil + } + isDone = rawResult == 0x07 + } + } + return Inclusion(rawValue: rawResult)! + } +} + +//===----------------------------------------------------------------------===// +// sortedOverlap(with:) +//===----------------------------------------------------------------------===// + +extension Sequence where Element: Comparable { + /// Returns how this sequence and the given sequence overlap, assuming both + /// are sorted. + /// + /// - Parameters: + /// - other: A sequence to compare to this sequence. + /// - Returns: The degree of inclusion between the sequences. + /// + /// - Complexity: O(*m*), where *m* is the lesser of the length of the + /// sequence and the length of `other`. + @inlinable + public func sortedOverlap(with other: S) -> Inclusion + where S.Element == Element { + return sortedOverlap(with: other, by: <) + } +} diff --git a/Tests/SwiftAlgorithmsTests/SortedInclusionTests.swift b/Tests/SwiftAlgorithmsTests/SortedInclusionTests.swift new file mode 100644 index 00000000..a0a21f25 --- /dev/null +++ b/Tests/SwiftAlgorithmsTests/SortedInclusionTests.swift @@ -0,0 +1,105 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Algorithms open source project +// +// Copyright (c) 2020 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 + +/// Unit tests for the `sortedOverlap` method and `Inclusion` type. +final class SortedInclusionTests: XCTestCase { + /// Check the `Inclusion` type's properties. + func testInclusion() { + XCTAssertEqualSequences(Inclusion.allCases, [ + .bothUninhabited, .onlyFirstInhabited, .onlySecondInhabited, + .dualExclusivesOnly, .sharedOnly, .firstExtendsSecond, + .secondExtendsFirst, .dualExclusivesAndShared + ]) + + XCTAssertEqualSequences(Inclusion.allCases.map(\.hasExclusivesToFirst), [ + false, true, false, true, false, true, false, true + ]) + XCTAssertEqualSequences(Inclusion.allCases.map(\.hasExclusivesToSecond), [ + false, false, true, true, false, false, true, true + ]) + XCTAssertEqualSequences(Inclusion.allCases.map(\.hasSharedElements), [ + false, false, false, false, true, true, true, true + ]) + + XCTAssertEqualSequences(Inclusion.allCases.map(\.areIdentical), [ + true, false, false, false, true, false, false, false + ]) + XCTAssertEqualSequences(Inclusion.allCases.map(\.doesFirstIncludeSecond), [ + true, true, false, false, true, true, false, false + ]) + XCTAssertEqualSequences(Inclusion.allCases.map(\.doesSecondIncludeFirst), [ + true, false, true, false, true, false, true, false + ]) + } + + /// Check when both sources are empty. + func testEmpty() { + let empty = EmptyCollection() + XCTAssertEqual(empty.sortedOverlap(with: empty), .bothUninhabited) + } + + /// Check when exactly one source is empty. + func testOnlyOneEmpty() { + let empty = EmptyCollection(), single = CollectionOfOne(1) + XCTAssertEqual(single.sortedOverlap(with: empty), .onlyFirstInhabited) + XCTAssertEqual(empty.sortedOverlap(with: single), .onlySecondInhabited) + } + + /// Check when there are no common elements. + func testDisjoint() { + let one = CollectionOfOne(1), two = CollectionOfOne(2) + XCTAssertEqual(one.sortedOverlap(with: two), .dualExclusivesOnly) + XCTAssertEqual(two.sortedOverlap(with: one), .dualExclusivesOnly) + // The order changes which comparison branch is used and which versus-nil + // case is used. + } + + /// Check when there are only common elements. + func testIdentical() { + let single = CollectionOfOne(1) + XCTAssertEqual(single.sortedOverlap(with: single), .sharedOnly) + } + + /// Check when the first source is a superset of the second. + func testFirstIncludesSecond() { + XCTAssertEqual([1, 2, 3, 5, 7].sortedOverlap(with: [1, 3, 5, 7]), + .firstExtendsSecond) + XCTAssertEqual([2, 4, 6, 8].sortedOverlap(with: [2, 4, 6]), + .firstExtendsSecond) + // The logic path differs if the last elements tie, or the first source's + // last element is bigger. (The second's last element can't be biggest.) + } + + /// Check when the second source is a superset of the first. + func testSecondIncludesFirst() { + XCTAssertEqual([1, 3, 5, 7].sortedOverlap(with: [1, 2, 3, 5, 7]), + .secondExtendsFirst) + XCTAssertEqual([2, 4, 6].sortedOverlap(with: [2, 4, 6, 8]), + .secondExtendsFirst) + // The logic path differs if the last elements tie, or the second source's + // last element is bigger. (The first's last element can't be biggest.) + } + + /// Check when there are shared and two-way exclusive elements. + func testPartialOverlap() { + XCTAssertEqual([3, 6, 9].sortedOverlap(with: [2, 4, 6, 8]), + .dualExclusivesAndShared) + XCTAssertEqual([1, 2, 4].sortedOverlap(with: [1, 4, 16]), + .dualExclusivesAndShared) + // For the three categories; exclusive to first, exclusive to second, and + // shared; if the third one encountered isn't from the last element(s) from + // a sequence(s), then the iteration will end early. The first example + // uses the short-circuit condition. + } +} From b8d6560ee231a0e22158b496d09294b9e3a35bd7 Mon Sep 17 00:00:00 2001 From: Daryle Walker Date: Thu, 29 Oct 2020 18:55:09 -0400 Subject: [PATCH 2/9] Tweak a type name and docs Rename the type expressing kinds of set overlap, to be less general. Explicitly list the preconditions for the set-overlap detection methods. --- CHANGELOG.md | 2 +- Sources/Algorithms/SortedInclusion.swift | 19 ++++++++++++++----- .../SortedInclusionTests.swift | 18 +++++++++--------- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dbb5851..59753773 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ package updates, you can specify your package dependency using - The `sortedOverlap(with: by:)` and `sortedOverlap(with:)` methods have been added. They report how much overlap two sorted sequences have, expressed by - the new `Inclusion` type. + the new `SetInclusion` type. --- diff --git a/Sources/Algorithms/SortedInclusion.swift b/Sources/Algorithms/SortedInclusion.swift index bd30e0c0..8436736a 100644 --- a/Sources/Algorithms/SortedInclusion.swift +++ b/Sources/Algorithms/SortedInclusion.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// /// The manner two (multi-)sets may overlap, including degenerate cases. -public enum Inclusion: UInt, CaseIterable { +public enum SetInclusion: UInt, CaseIterable { /// Neither source had any elements. case bothUninhabited /// Only the first source had any elements. @@ -29,7 +29,7 @@ public enum Inclusion: UInt, CaseIterable { case dualExclusivesAndShared } -extension Inclusion { +extension SetInclusion { /// Whether there are elements exclusive to the first source. @inlinable public var hasExclusivesToFirst: Bool { rawValue & 0x01 != 0 } /// Whether there are elements exclusive to the second source. @@ -66,6 +66,11 @@ extension Sequence { /// and `c` are incomparable, then `a` and `c` are also incomparable. /// (Transitive incomparability) /// + /// - Precondition: + /// - Both the receiver and `other` are sorted according to + /// `areInIncreasingOrder`. + /// - Both the receiver and `other` should be finite. + /// /// - Parameters: /// - other: A sequence to compare to this sequence. /// - areInIncreasingOrder: A predicate that returns `true` if its first @@ -78,7 +83,7 @@ extension Sequence { public func sortedOverlap( with other: S, by areInIncreasingOrder: (Element, Element) throws -> Bool - ) rethrows -> Inclusion where S.Element == Element { + ) rethrows -> SetInclusion where S.Element == Element { var rawResult: UInt = 0, cache, otherCache: Element?, isDone = false var iterator = makeIterator(), otherIterator = other.makeIterator() while !isDone { @@ -108,7 +113,7 @@ extension Sequence { isDone = rawResult == 0x07 } } - return Inclusion(rawValue: rawResult)! + return SetInclusion(rawValue: rawResult)! } } @@ -120,6 +125,10 @@ extension Sequence where Element: Comparable { /// Returns how this sequence and the given sequence overlap, assuming both /// are sorted. /// + /// - Precondition: + /// - Both the receiver and `other` are sorted. + /// - Both the receiver and `other` should be finite. + /// /// - Parameters: /// - other: A sequence to compare to this sequence. /// - Returns: The degree of inclusion between the sequences. @@ -127,7 +136,7 @@ extension Sequence where Element: Comparable { /// - Complexity: O(*m*), where *m* is the lesser of the length of the /// sequence and the length of `other`. @inlinable - public func sortedOverlap(with other: S) -> Inclusion + public func sortedOverlap(with other: S) -> SetInclusion where S.Element == Element { return sortedOverlap(with: other, by: <) } diff --git a/Tests/SwiftAlgorithmsTests/SortedInclusionTests.swift b/Tests/SwiftAlgorithmsTests/SortedInclusionTests.swift index a0a21f25..e4732fa3 100644 --- a/Tests/SwiftAlgorithmsTests/SortedInclusionTests.swift +++ b/Tests/SwiftAlgorithmsTests/SortedInclusionTests.swift @@ -12,33 +12,33 @@ import XCTest import Algorithms -/// Unit tests for the `sortedOverlap` method and `Inclusion` type. +/// Unit tests for the `sortedOverlap` method and `SetInclusion` type. final class SortedInclusionTests: XCTestCase { - /// Check the `Inclusion` type's properties. + /// Check the `SetInclusion` type's properties. func testInclusion() { - XCTAssertEqualSequences(Inclusion.allCases, [ + XCTAssertEqualSequences(SetInclusion.allCases, [ .bothUninhabited, .onlyFirstInhabited, .onlySecondInhabited, .dualExclusivesOnly, .sharedOnly, .firstExtendsSecond, .secondExtendsFirst, .dualExclusivesAndShared ]) - XCTAssertEqualSequences(Inclusion.allCases.map(\.hasExclusivesToFirst), [ + XCTAssertEqualSequences(SetInclusion.allCases.map(\.hasExclusivesToFirst), [ false, true, false, true, false, true, false, true ]) - XCTAssertEqualSequences(Inclusion.allCases.map(\.hasExclusivesToSecond), [ + XCTAssertEqualSequences(SetInclusion.allCases.map(\.hasExclusivesToSecond), [ false, false, true, true, false, false, true, true ]) - XCTAssertEqualSequences(Inclusion.allCases.map(\.hasSharedElements), [ + XCTAssertEqualSequences(SetInclusion.allCases.map(\.hasSharedElements), [ false, false, false, false, true, true, true, true ]) - XCTAssertEqualSequences(Inclusion.allCases.map(\.areIdentical), [ + XCTAssertEqualSequences(SetInclusion.allCases.map(\.areIdentical), [ true, false, false, false, true, false, false, false ]) - XCTAssertEqualSequences(Inclusion.allCases.map(\.doesFirstIncludeSecond), [ + XCTAssertEqualSequences(SetInclusion.allCases.map(\.doesFirstIncludeSecond), [ true, true, false, false, true, true, false, false ]) - XCTAssertEqualSequences(Inclusion.allCases.map(\.doesSecondIncludeFirst), [ + XCTAssertEqualSequences(SetInclusion.allCases.map(\.doesSecondIncludeFirst), [ true, false, true, false, true, false, true, false ]) } From 5afb68653c0c65c4acb8a12564f2b34e8bbba1ea Mon Sep 17 00:00:00 2001 From: Daryle Walker Date: Thu, 29 Oct 2020 18:57:54 -0400 Subject: [PATCH 3/9] Tweak a type name Forgot to update a file for a type rename from the last change. --- Guides/SortedInclusion.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Guides/SortedInclusion.md b/Guides/SortedInclusion.md index 29b6bda4..53e53e4a 100644 --- a/Guides/SortedInclusion.md +++ b/Guides/SortedInclusion.md @@ -13,19 +13,19 @@ The inclusion-detection methods are declared as extensions to `Sequence`. The overload that defaults comparisons to the standard less-than operator is constrained to when the `Element` type conforms to `Comparable`. -A reported inclusion state is expressed with the `Inclusion` type. This state +A reported inclusion state is expressed with the `SetInclusion` type. This state is based on the existence of elements that are shared, exclusive to the first sequence, and exclusive to the second sequence. This includes all the degenerate combinations. Convenience properties are included for easy tests. ```swift -enum Inclusion { +enum SetInclusion { case bothUninhabited, onlyFirstInhabited, onlySecondInhabited, dualExclusivesOnly, sharedOnly, firstExtendsSecond, secondExtendsFirst, dualExclusivesAndShared } -extension Inclusion { +extension SetInclusion { var hasExclusivesToFirst: Bool { get } var hasExclusivesToSecond: Bool { get } var hasSharedElements: Bool { get } @@ -38,13 +38,13 @@ extension Sequence { func sortedOverlap( with other: S, by areInIncreasingOrder: (Element, Element) throws -> Bool - ) rethrows -> Inclusion where S.Element == Element + ) rethrows -> SetInclusion where S.Element == Element } extension Sequence where Element: Comparable { func sortedOverlap( with other: S - ) -> Inclusion where S.Element == Element + ) -> SetInclusion where S.Element == Element } ``` From 354e4c71f55a590dcfec976170fe8b9f3f047827 Mon Sep 17 00:00:00 2001 From: Daryle Walker Date: Tue, 15 Mar 2022 00:48:16 -0400 Subject: [PATCH 4/9] Tweak meta-documentation Add a reference to the pull-request link. Change location of summary. --- CHANGELOG.md | 3 ++- README.md | 5 +---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe57050f..b89cb19f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ This project follows semantic versioning. - The `sortedOverlap(with: by:)` and `sortedOverlap(with:)` methods have been added. They report how much overlap two sorted sequences have, expressed by - the new `SetInclusion` type. + the new `SetInclusion` type. ([#38]) --- @@ -292,6 +292,7 @@ This changelog's format is based on [Keep a Changelog](https://keepachangelog.co [#24]: https://github.com/apple/swift-algorithms/pull/24 [#31]: https://github.com/apple/swift-algorithms/pull/31 [#35]: https://github.com/apple/swift-algorithms/pull/35 +[#38]: https://github.com/apple/swift-algorithms/pull/38 [#46]: https://github.com/apple/swift-algorithms/pull/46 [#51]: https://github.com/apple/swift-algorithms/pull/51 [#54]: https://github.com/apple/swift-algorithms/pull/54 diff --git a/README.md b/README.md index fdb6488d..ad1efe8a 100644 --- a/README.md +++ b/README.md @@ -40,10 +40,6 @@ Read more about the package, and the intent behind it, in the [announcement on s - [`min(count:)`, `max(count:)`, `min(count:sortedBy:)`, `max(count:sortedBy:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/MinMax.md): Returns the smallest or largest elements of a collection, sorted by a predicate. -#### Comparisons - --[`sortedOverlap(with: by:)`, `sortedOverlap(with:)`](./Guides/SortedInclusion.md): Reports the degree two sorted sequences overlap. - #### Other useful operations - [`adjacentPairs()`](https://github.com/apple/swift-algorithms/blob/main/Guides/AdjacentPairs.md): Lazily iterates over tuples of adjacent elements. @@ -55,6 +51,7 @@ Read more about the package, and the intent behind it, in the [announcement on s - [`reductions(_:)`, `reductions(_:_:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Reductions.md): Returns all the intermediate states of reducing the elements of a sequence or collection. - [`split(maxSplits:omittingEmptySubsequences:whereSeparator)`, `split(separator:maxSplits:omittingEmptySubsequences)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Split.md): Lazy versions of the Standard Library's eager operations that split sequences and collections into subsequences separated by the specified separator element. - [`windows(ofCount:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Windows.md): Breaks a collection into overlapping subsequences where elements are slices from the original collection. +- [`sortedOverlap(with:by:)`, `sortedOverlap(with:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/SortedInclusion.md): Reports the degree two sorted sequences overlap. ## Adding Swift Algorithms as a Dependency From b8b699cc45f8280d1e99e89e0d70f5b2fc48b226 Mon Sep 17 00:00:00 2001 From: Daryle Walker Date: Tue, 15 Mar 2022 01:32:28 -0400 Subject: [PATCH 5/9] Tweak documentation --- Sources/Algorithms/SortedInclusion.swift | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Sources/Algorithms/SortedInclusion.swift b/Sources/Algorithms/SortedInclusion.swift index 8436736a..abd78c5f 100644 --- a/Sources/Algorithms/SortedInclusion.swift +++ b/Sources/Algorithms/SortedInclusion.swift @@ -66,17 +66,16 @@ extension Sequence { /// and `c` are incomparable, then `a` and `c` are also incomparable. /// (Transitive incomparability) /// - /// - Precondition: - /// - Both the receiver and `other` are sorted according to - /// `areInIncreasingOrder`. - /// - Both the receiver and `other` should be finite. + /// - Precondition: Both the receiver and `other` are sorted according to + /// `areInIncreasingOrder`; and both should be finite. /// /// - Parameters: /// - other: A sequence to compare to this sequence. /// - areInIncreasingOrder: A predicate that returns `true` if its first /// argument should be ordered before its second argument; otherwise, /// `false`. - /// - Returns: The degree of inclusion between the sequences. + /// - Returns: The degree of inclusion between the sequences. The receiver is + /// considered the first source, and `other` second. /// /// - Complexity: O(*m*), where *m* is the lesser of the length of the /// sequence and the length of `other`. @@ -125,13 +124,13 @@ extension Sequence where Element: Comparable { /// Returns how this sequence and the given sequence overlap, assuming both /// are sorted. /// - /// - Precondition: - /// - Both the receiver and `other` are sorted. - /// - Both the receiver and `other` should be finite. + /// - Precondition: Both the receiver and `other` are sorted; and both should + /// be finite. /// /// - Parameters: /// - other: A sequence to compare to this sequence. - /// - Returns: The degree of inclusion between the sequences. + /// - Returns: The degree of inclusion between the sequences. The receiver is + /// considered the first source, and `other` second. /// /// - Complexity: O(*m*), where *m* is the lesser of the length of the /// sequence and the length of `other`. From 8cc63d6e6e0a3334562244149721b92208c24585 Mon Sep 17 00:00:00 2001 From: Daryle Walker Date: Tue, 15 Mar 2022 02:00:45 -0400 Subject: [PATCH 6/9] Add example to documentation --- Guides/SortedInclusion.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Guides/SortedInclusion.md b/Guides/SortedInclusion.md index 53e53e4a..deab0a9c 100644 --- a/Guides/SortedInclusion.md +++ b/Guides/SortedInclusion.md @@ -5,7 +5,15 @@ Methods to find how much two sorted sequences overlap. -(To-do: expand on this.) +```swift +if (1...7).sortedOverlap(with: [1, 5, 6]).doesFirstIncludeSecond { + print("The range is a superset of the array.") +} +``` + +The result is an enumeration type, instead of a simple `Bool`, so an useful +answer can be extracted if which source sequence is actually the superset is +called from the wrong spot. ## Detailed Design From eb2a4984495089712e1c5d590800d3f02178f135 Mon Sep 17 00:00:00 2001 From: Daryle Walker Date: Sat, 19 Mar 2022 18:26:41 -0400 Subject: [PATCH 7/9] Rename the methods Change the name of the "sortedOverlap" methods to "degreeOfInclusion," to clarify what is being calculated. --- CHANGELOG.md | 6 ++--- Guides/SortedInclusion.md | 12 +++++----- README.md | 2 +- Sources/Algorithms/SortedInclusion.swift | 10 ++++---- .../SortedInclusionTests.swift | 24 +++++++++---------- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b89cb19f..268368e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,9 +10,9 @@ This project follows semantic versioning. ### Additions -- The `sortedOverlap(with: by:)` and `sortedOverlap(with:)` methods have been - added. They report how much overlap two sorted sequences have, expressed by - the new `SetInclusion` type. ([#38]) +- The `degreeOfInclusion(with:by:)` and `degreeOfInclusion(with:)` methods have + been added. They report how much overlap two sorted sequences have, expressed + by the `SetInclusion` type. ([#38]) --- diff --git a/Guides/SortedInclusion.md b/Guides/SortedInclusion.md index deab0a9c..7af409ac 100644 --- a/Guides/SortedInclusion.md +++ b/Guides/SortedInclusion.md @@ -6,7 +6,7 @@ Methods to find how much two sorted sequences overlap. ```swift -if (1...7).sortedOverlap(with: [1, 5, 6]).doesFirstIncludeSecond { +if (1...7).degreeOfInclusion(with: [1, 5, 6]).doesFirstIncludeSecond { print("The range is a superset of the array.") } ``` @@ -43,14 +43,14 @@ extension SetInclusion { } extension Sequence { - func sortedOverlap( + func degreeOfInclusion( with other: S, by areInIncreasingOrder: (Element, Element) throws -> Bool ) rethrows -> SetInclusion where S.Element == Element } extension Sequence where Element: Comparable { - func sortedOverlap( + func degreeOfInclusion( with other: S ) -> SetInclusion where S.Element == Element } @@ -64,11 +64,11 @@ O(_n_) operations, where _n_ is the length of the shorter source. ### Comparison with other languages **C++:** The `` library defines the `includes` function, whose -functionality is part of the semantics of `sortedOverlap`. The `includes` +functionality is part of the semantics of `degreeOfInclusion`. The `includes` function only detects of the second sequence is included within the first; it doesn't notify if the inclusion is degenerate, or if inclusion fails because -it's actually reversed, both of which `sortedOverlap` can do. To get the +it's actually reversed, both of which `degreeOfInclusion` can do. To get the direct functionality of `includes`, check the `doesFirstIncludeSecond` property -of the return value from `sortedOverlap`. +of the return value from `degreeOfInclusion`. (To-do: add other languages.) diff --git a/README.md b/README.md index ad1efe8a..8921a83d 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Read more about the package, and the intent behind it, in the [announcement on s - [`reductions(_:)`, `reductions(_:_:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Reductions.md): Returns all the intermediate states of reducing the elements of a sequence or collection. - [`split(maxSplits:omittingEmptySubsequences:whereSeparator)`, `split(separator:maxSplits:omittingEmptySubsequences)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Split.md): Lazy versions of the Standard Library's eager operations that split sequences and collections into subsequences separated by the specified separator element. - [`windows(ofCount:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Windows.md): Breaks a collection into overlapping subsequences where elements are slices from the original collection. -- [`sortedOverlap(with:by:)`, `sortedOverlap(with:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/SortedInclusion.md): Reports the degree two sorted sequences overlap. +- [`degreeOfInclusion(with:by:)`, `degreeOfInclusion(with:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/SortedInclusion.md): Reports the degree two sorted sequences overlap. ## Adding Swift Algorithms as a Dependency diff --git a/Sources/Algorithms/SortedInclusion.swift b/Sources/Algorithms/SortedInclusion.swift index abd78c5f..003a161c 100644 --- a/Sources/Algorithms/SortedInclusion.swift +++ b/Sources/Algorithms/SortedInclusion.swift @@ -46,7 +46,7 @@ extension SetInclusion { } //===----------------------------------------------------------------------===// -// sortedOverlap(with: by:) +// degreeOfInclusion(with:by:) //===----------------------------------------------------------------------===// extension Sequence { @@ -79,7 +79,7 @@ extension Sequence { /// /// - Complexity: O(*m*), where *m* is the lesser of the length of the /// sequence and the length of `other`. - public func sortedOverlap( + public func degreeOfInclusion( with other: S, by areInIncreasingOrder: (Element, Element) throws -> Bool ) rethrows -> SetInclusion where S.Element == Element { @@ -117,7 +117,7 @@ extension Sequence { } //===----------------------------------------------------------------------===// -// sortedOverlap(with:) +// degreeOfInclusion(with:) //===----------------------------------------------------------------------===// extension Sequence where Element: Comparable { @@ -135,8 +135,8 @@ extension Sequence where Element: Comparable { /// - Complexity: O(*m*), where *m* is the lesser of the length of the /// sequence and the length of `other`. @inlinable - public func sortedOverlap(with other: S) -> SetInclusion + public func degreeOfInclusion(with other: S) -> SetInclusion where S.Element == Element { - return sortedOverlap(with: other, by: <) + return degreeOfInclusion(with: other, by: <) } } diff --git a/Tests/SwiftAlgorithmsTests/SortedInclusionTests.swift b/Tests/SwiftAlgorithmsTests/SortedInclusionTests.swift index e4732fa3..0a7f1f18 100644 --- a/Tests/SwiftAlgorithmsTests/SortedInclusionTests.swift +++ b/Tests/SwiftAlgorithmsTests/SortedInclusionTests.swift @@ -46,21 +46,21 @@ final class SortedInclusionTests: XCTestCase { /// Check when both sources are empty. func testEmpty() { let empty = EmptyCollection() - XCTAssertEqual(empty.sortedOverlap(with: empty), .bothUninhabited) + XCTAssertEqual(empty.degreeOfInclusion(with: empty), .bothUninhabited) } /// Check when exactly one source is empty. func testOnlyOneEmpty() { let empty = EmptyCollection(), single = CollectionOfOne(1) - XCTAssertEqual(single.sortedOverlap(with: empty), .onlyFirstInhabited) - XCTAssertEqual(empty.sortedOverlap(with: single), .onlySecondInhabited) + XCTAssertEqual(single.degreeOfInclusion(with: empty), .onlyFirstInhabited) + XCTAssertEqual(empty.degreeOfInclusion(with: single), .onlySecondInhabited) } /// Check when there are no common elements. func testDisjoint() { let one = CollectionOfOne(1), two = CollectionOfOne(2) - XCTAssertEqual(one.sortedOverlap(with: two), .dualExclusivesOnly) - XCTAssertEqual(two.sortedOverlap(with: one), .dualExclusivesOnly) + XCTAssertEqual(one.degreeOfInclusion(with: two), .dualExclusivesOnly) + XCTAssertEqual(two.degreeOfInclusion(with: one), .dualExclusivesOnly) // The order changes which comparison branch is used and which versus-nil // case is used. } @@ -68,14 +68,14 @@ final class SortedInclusionTests: XCTestCase { /// Check when there are only common elements. func testIdentical() { let single = CollectionOfOne(1) - XCTAssertEqual(single.sortedOverlap(with: single), .sharedOnly) + XCTAssertEqual(single.degreeOfInclusion(with: single), .sharedOnly) } /// Check when the first source is a superset of the second. func testFirstIncludesSecond() { - XCTAssertEqual([1, 2, 3, 5, 7].sortedOverlap(with: [1, 3, 5, 7]), + XCTAssertEqual([1, 2, 3, 5, 7].degreeOfInclusion(with: [1, 3, 5, 7]), .firstExtendsSecond) - XCTAssertEqual([2, 4, 6, 8].sortedOverlap(with: [2, 4, 6]), + XCTAssertEqual([2, 4, 6, 8].degreeOfInclusion(with: [2, 4, 6]), .firstExtendsSecond) // The logic path differs if the last elements tie, or the first source's // last element is bigger. (The second's last element can't be biggest.) @@ -83,9 +83,9 @@ final class SortedInclusionTests: XCTestCase { /// Check when the second source is a superset of the first. func testSecondIncludesFirst() { - XCTAssertEqual([1, 3, 5, 7].sortedOverlap(with: [1, 2, 3, 5, 7]), + XCTAssertEqual([1, 3, 5, 7].degreeOfInclusion(with: [1, 2, 3, 5, 7]), .secondExtendsFirst) - XCTAssertEqual([2, 4, 6].sortedOverlap(with: [2, 4, 6, 8]), + XCTAssertEqual([2, 4, 6].degreeOfInclusion(with: [2, 4, 6, 8]), .secondExtendsFirst) // The logic path differs if the last elements tie, or the second source's // last element is bigger. (The first's last element can't be biggest.) @@ -93,9 +93,9 @@ final class SortedInclusionTests: XCTestCase { /// Check when there are shared and two-way exclusive elements. func testPartialOverlap() { - XCTAssertEqual([3, 6, 9].sortedOverlap(with: [2, 4, 6, 8]), + XCTAssertEqual([3, 6, 9].degreeOfInclusion(with: [2, 4, 6, 8]), .dualExclusivesAndShared) - XCTAssertEqual([1, 2, 4].sortedOverlap(with: [1, 4, 16]), + XCTAssertEqual([1, 2, 4].degreeOfInclusion(with: [1, 4, 16]), .dualExclusivesAndShared) // For the three categories; exclusive to first, exclusive to second, and // shared; if the third one encountered isn't from the last element(s) from From ae0b0d747a174ee2ad4503efb4872c3435a9e8f3 Mon Sep 17 00:00:00 2001 From: Daryle Walker Date: Sat, 19 Mar 2022 18:33:00 -0400 Subject: [PATCH 8/9] Rename the files Change the name of the source, test, and guide files to match the updated method name. Update their link in the README file. --- Guides/{SortedInclusion.md => DegreeOfInclusion.md} | 4 ++-- README.md | 2 +- .../{SortedInclusion.swift => DegreeOfInclusion.swift} | 0 .../{SortedInclusionTests.swift => DegreeOfInclusion.swift} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename Guides/{SortedInclusion.md => DegreeOfInclusion.md} (95%) rename Sources/Algorithms/{SortedInclusion.swift => DegreeOfInclusion.swift} (100%) rename Tests/SwiftAlgorithmsTests/{SortedInclusionTests.swift => DegreeOfInclusion.swift} (100%) diff --git a/Guides/SortedInclusion.md b/Guides/DegreeOfInclusion.md similarity index 95% rename from Guides/SortedInclusion.md rename to Guides/DegreeOfInclusion.md index 7af409ac..96aa9a42 100644 --- a/Guides/SortedInclusion.md +++ b/Guides/DegreeOfInclusion.md @@ -1,7 +1,7 @@ # Sorted Sequence Inclusion -[[Source](../Sources/Algorithms/SortedInclusion.swift) | - [Tests](../Tests/SwiftAlgorithmsTests/SortedInclusionTests.swift)] +[[Source](../Sources/Algorithms/DegreeOfInclusion.swift) | + [Tests](../Tests/SwiftAlgorithmsTests/DegreeOfInclusionTests.swift)] Methods to find how much two sorted sequences overlap. diff --git a/README.md b/README.md index 8921a83d..4c368059 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Read more about the package, and the intent behind it, in the [announcement on s - [`reductions(_:)`, `reductions(_:_:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Reductions.md): Returns all the intermediate states of reducing the elements of a sequence or collection. - [`split(maxSplits:omittingEmptySubsequences:whereSeparator)`, `split(separator:maxSplits:omittingEmptySubsequences)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Split.md): Lazy versions of the Standard Library's eager operations that split sequences and collections into subsequences separated by the specified separator element. - [`windows(ofCount:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Windows.md): Breaks a collection into overlapping subsequences where elements are slices from the original collection. -- [`degreeOfInclusion(with:by:)`, `degreeOfInclusion(with:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/SortedInclusion.md): Reports the degree two sorted sequences overlap. +- [`degreeOfInclusion(with:by:)`, `degreeOfInclusion(with:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/DegreeOfInclusion.md): Reports the degree two sorted sequences overlap. ## Adding Swift Algorithms as a Dependency diff --git a/Sources/Algorithms/SortedInclusion.swift b/Sources/Algorithms/DegreeOfInclusion.swift similarity index 100% rename from Sources/Algorithms/SortedInclusion.swift rename to Sources/Algorithms/DegreeOfInclusion.swift diff --git a/Tests/SwiftAlgorithmsTests/SortedInclusionTests.swift b/Tests/SwiftAlgorithmsTests/DegreeOfInclusion.swift similarity index 100% rename from Tests/SwiftAlgorithmsTests/SortedInclusionTests.swift rename to Tests/SwiftAlgorithmsTests/DegreeOfInclusion.swift From ca4a143ede6b49c101d4e95ff627ecf685da2403 Mon Sep 17 00:00:00 2001 From: Daryle Walker Date: Sat, 19 Mar 2022 18:46:39 -0400 Subject: [PATCH 9/9] Improve documentation Change some paragraphs with better explanations on design and usage. Change punctuation spacing to match the project's style. --- Guides/DegreeOfInclusion.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Guides/DegreeOfInclusion.md b/Guides/DegreeOfInclusion.md index 96aa9a42..8e81bb79 100644 --- a/Guides/DegreeOfInclusion.md +++ b/Guides/DegreeOfInclusion.md @@ -11,20 +11,23 @@ if (1...7).degreeOfInclusion(with: [1, 5, 6]).doesFirstIncludeSecond { } ``` -The result is an enumeration type, instead of a simple `Bool`, so an useful -answer can be extracted if which source sequence is actually the superset is -called from the wrong spot. +The result is an enumeration detailing the precise containment relationship, if +any. If the result was `Bool`, then the method would need to be called again +(with swapped arguments) to confirm the actual inclusion degree. ## Detailed Design -The inclusion-detection methods are declared as extensions to `Sequence`. The +The inclusion-detection methods are declared as extensions to `Sequence`. The overload that defaults comparisons to the standard less-than operator is constrained to when the `Element` type conforms to `Comparable`. -A reported inclusion state is expressed with the `SetInclusion` type. This state +A reported inclusion state is expressed with the `SetInclusion` type. This state is based on the existence of elements that are shared, exclusive to the first sequence, and exclusive to the second sequence. This includes all the -degenerate combinations. Convenience properties are included for easy tests. +degenerate combinations, which are the ones where at least one source is empty. +Use the convenience properties `doesFirstIncludeSecond` and +`doesSecondIncludeFirst` (and `areIdentical`) to actually check if one source is +a superset of the other. ```swift enum SetInclusion { @@ -64,10 +67,10 @@ O(_n_) operations, where _n_ is the length of the shorter source. ### Comparison with other languages **C++:** The `` library defines the `includes` function, whose -functionality is part of the semantics of `degreeOfInclusion`. The `includes` +functionality is part of the semantics of `degreeOfInclusion`. The `includes` function only detects of the second sequence is included within the first; it doesn't notify if the inclusion is degenerate, or if inclusion fails because -it's actually reversed, both of which `degreeOfInclusion` can do. To get the +it's actually reversed, both of which `degreeOfInclusion` can do. To get the direct functionality of `includes`, check the `doesFirstIncludeSecond` property of the return value from `degreeOfInclusion`.