From 87ed17590d00f370b9695785b95a08b34092d805 Mon Sep 17 00:00:00 2001 From: Steffan Andrews Date: Fri, 17 Dec 2021 16:31:54 -0800 Subject: [PATCH] Added `.contains(RangeExpression)` for all relevant `RangeExpression` types --- Package.swift | 4 + Sources/OTCore/Extensions/Swift/Ranges.swift | 262 +++++++++++++-- .../Extensions/Swift/Ranges Tests.swift | 303 +++++++++++++++++- 3 files changed, 546 insertions(+), 23 deletions(-) diff --git a/Package.swift b/Package.swift index 7c86e17..689b5ec 100644 --- a/Package.swift +++ b/Package.swift @@ -7,6 +7,10 @@ let package = Package( name: "OTCore", + platforms: [ + .macOS(.v10_12) + ], + products: [ .library( name: "OTCore", diff --git a/Sources/OTCore/Extensions/Swift/Ranges.swift b/Sources/OTCore/Extensions/Swift/Ranges.swift index 5867dfa..774aedc 100644 --- a/Sources/OTCore/Extensions/Swift/Ranges.swift +++ b/Sources/OTCore/Extensions/Swift/Ranges.swift @@ -17,17 +17,17 @@ extension Comparable { /// /// Example: /// - /// 5.isContained(in: 1...4) + /// 5.isContained(in: 1...4) // == false /// /// To test if `self` is NOT contained in `range`: /// - /// !5.isContained(in: 1...4) + /// !5.isContained(in: 1...4) // == true /// // or - /// 5.isContained(in: 1...4).toggled() + /// 5.isContained(in: 1...4).toggled() // == true /// @inlinable public - func isContained(in range: T) -> Bool - where Self == T.Bound { + func isContained(in range: R) -> Bool + where Self == R.Bound { range.contains(self) @@ -57,9 +57,9 @@ extension Comparable { /// } /// @inlinable public - func ifContained(in range: T, - then newValue: @autoclosure () throws -> T.Bound) rethrows -> T.Bound - where Self == T.Bound { + func ifContained(in range: R, + then newValue: @autoclosure () throws -> R.Bound) rethrows -> R.Bound + where Self == R.Bound { try range.contains(self) ? newValue() : self @@ -82,9 +82,9 @@ extension Comparable { /// } // 700 /// @inlinable public - func ifContained(in range: T, - then newValue: (Self) throws -> T.Bound) rethrows -> T.Bound - where Self == T.Bound { + func ifContained(in range: R, + then newValue: (Self) throws -> R.Bound) rethrows -> R.Bound + where Self == R.Bound { try range.contains(self) ? newValue(self) : self @@ -109,9 +109,9 @@ extension Comparable { /// } nil /// @inlinable public - func ifContained(in range: T, + func ifContained(in range: R, then newValue: (Self) throws -> U) rethrows -> U? - where Self == T.Bound { + where Self == R.Bound { try range.contains(self) ? newValue(self) : nil @@ -141,9 +141,9 @@ extension Comparable { /// } /// @inlinable public - func ifNotContained(in range: T, - then newValue: @autoclosure () throws -> T.Bound) rethrows -> T.Bound - where Self == T.Bound { + func ifNotContained(in range: R, + then newValue: @autoclosure () throws -> R.Bound) rethrows -> R.Bound + where Self == R.Bound { try range.contains(self) ? self : newValue() @@ -166,9 +166,9 @@ extension Comparable { /// } // 705 /// @inlinable public - func ifNotContained(in range: T, - then newValue: (Self) throws -> T.Bound) rethrows -> T.Bound - where Self == T.Bound { + func ifNotContained(in range: R, + then newValue: (Self) throws -> R.Bound) rethrows -> R.Bound + where Self == R.Bound { try range.contains(self) ? self : newValue(self) @@ -193,9 +193,9 @@ extension Comparable { /// } // Optional("700 not in range") /// @inlinable public - func ifNotContained(in range: T, + func ifNotContained(in range: R, then newValue: (Self) throws -> U) rethrows -> U? - where Self == T.Bound { + where Self == R.Bound { try range.contains(self) ? nil : newValue(self) @@ -204,6 +204,223 @@ extension Comparable { } +// MARK: - ClosedRange.contains(range) + +// these don't make sense to implement since they would always return false: +// - ClosedRange.contains(PartialRangeFrom) +// - ClosedRange.contains(PartialRangeUpTo) +// - ClosedRange.contains(PartialRangeThrough) + +extension ClosedRange { + + /// **OTCore:** + /// Returns `true` if base range contains the given range. + @inlinable public + func contains(_ other: ClosedRange) -> Bool { + + other.lowerBound >= lowerBound && + other.upperBound <= upperBound + + } + +} + +extension ClosedRange where Bound : Strideable, + Bound.Stride : SignedInteger { + + /// **OTCore:** + /// Returns `true` if base range contains the given range. + @inlinable public + func contains(_ other: Range) -> Bool { + + guard let unwrappedMax = self.max() else { return false } + + guard !other.isEmpty else { return false } + + return other.lowerBound >= lowerBound && + other.upperBound.advanced(by: -1) <= unwrappedMax + + } + +} + + +// MARK: - Range.contains(range) + +// these don't make sense to implement since they would always return false: +// - Range.contains(PartialRangeFrom) +// - Range.contains(PartialRangeUpTo) +// - Range.contains(PartialRangeThrough) + +extension Range { + + /// **OTCore:** + /// Returns `true` if base range contains the given range. + @inlinable public + func contains(_ other: ClosedRange) -> Bool { + + other.lowerBound >= lowerBound && + other.upperBound < upperBound + + } + +} + +extension Range where Bound : Strideable, + Bound.Stride : SignedInteger { + + /// **OTCore:** + /// Returns `true` if base range contains the given range. + @inlinable public + func contains(_ other: Range) -> Bool { + + other.lowerBound >= lowerBound && + other.upperBound <= upperBound + + } + +} + + +// MARK: - PartialRangeFrom.contains(range) + +// these don't make sense to implement since they would always return false: +// - PartialRangeFrom.contains(PartialRangeUpTo) +// - PartialRangeFrom.contains(PartialRangeThrough) + +extension PartialRangeFrom { + + /// **OTCore:** + /// Returns `true` if base range contains the given range. + @inlinable public + func contains(_ other: ClosedRange) -> Bool { + + other.lowerBound >= lowerBound + + } + + /// **OTCore:** + /// Returns `true` if base range contains the given range. + @inlinable public + func contains(_ other: Range) -> Bool { + + other.lowerBound >= lowerBound + + } + + /// **OTCore:** + /// Returns `true` if base range contains the given range. + @inlinable public + func contains(_ other: PartialRangeFrom) -> Bool { + + other.lowerBound >= lowerBound + + } + +} + + +// MARK: - PartialRangeThrough.contains(range) + +// these don't make sense to implement since they would always return false: +// - PartialRangeThrough.contains(PartialRangeFrom) + +extension PartialRangeThrough { + + /// **OTCore:** + /// Returns `true` if base range contains the given range. + @inlinable public + func contains(_ other: ClosedRange) -> Bool { + + other.upperBound <= upperBound + + } +} + +extension PartialRangeThrough where Bound : BinaryInteger, + Bound.Stride : FixedWidthInteger { + + /// **OTCore:** + /// Returns `true` if base range contains the given range. + @inlinable public + func contains(_ other: Range) -> Bool { + + guard other.upperBound > Bound.Stride.min else { return false } + + return other.upperBound.advanced(by: -1) <= upperBound + + } + +} + +extension PartialRangeThrough { + + /// **OTCore:** + /// Returns `true` if base range contains the given range. + @inlinable public + func contains(_ other: PartialRangeThrough) -> Bool { + + other.upperBound <= upperBound + + } + + /// **OTCore:** + /// Returns `true` if base range contains the given range. + @inlinable public + func contains(_ other: PartialRangeUpTo) -> Bool { + + other.upperBound < upperBound + + } + +} + +// MARK: - PartialRangeUpTo.contains(range) + +// these don't make sense to implement since they would always return false: +// - PartialRangeUpTo.contains(PartialRangeFrom) + +extension PartialRangeUpTo { + + /// **OTCore:** + /// Returns `true` if base range contains the given range. + @inlinable public + func contains(_ other: ClosedRange) -> Bool { + + other.upperBound < upperBound + + } + + /// **OTCore:** + /// Returns `true` if base range contains the given range. + @inlinable public + func contains(_ other: Range) -> Bool { + + other.upperBound <= upperBound + + } + + /// **OTCore:** + /// Returns `true` if base range contains the given range. + @inlinable public + func contains(_ other: PartialRangeThrough) -> Bool { + + other.upperBound < upperBound + + } + + /// **OTCore:** + /// Returns `true` if base range contains the given range. + @inlinable public + func contains(_ other: PartialRangeUpTo) -> Bool { + + other.upperBound <= upperBound + + } + +} + + // MARK: - .clamped(to:) extension Comparable { @@ -213,7 +430,8 @@ extension Comparable { // ie: "a".clamped(to: "b"..."h") /// **OTCore:** /// Returns the value clamped to the passed range. - @inlinable public func clamped(to limits: ClosedRange) -> Self { + @inlinable public + func clamped(to limits: ClosedRange) -> Self { min(max(self, limits.lowerBound), limits.upperBound) diff --git a/Tests/OTCoreTests/Extensions/Swift/Ranges Tests.swift b/Tests/OTCoreTests/Extensions/Swift/Ranges Tests.swift index 5391959..0a6cd6f 100644 --- a/Tests/OTCoreTests/Extensions/Swift/Ranges Tests.swift +++ b/Tests/OTCoreTests/Extensions/Swift/Ranges Tests.swift @@ -294,7 +294,308 @@ class Extensions_Swift_Ranges_Tests: XCTestCase { } - func testNumberClampedToRanges() { + func testClosedRange_contains_ClosedRange() { + + XCTAssertTrue( (1...10).contains( 1 ... 1)) + XCTAssertTrue( (1...10).contains( 1 ... 10)) + XCTAssertTrue( (1...10).contains(10 ... 10)) + + XCTAssertFalse((1...10).contains( 0 ... 0)) + XCTAssertFalse((1...10).contains( 0 ... 1)) + XCTAssertFalse((1...10).contains( 0 ... 2)) + XCTAssertFalse((1...10).contains( 0 ... 10)) + XCTAssertFalse((1...10).contains( 9 ... 11)) + XCTAssertFalse((1...10).contains(10 ... 11)) + XCTAssertFalse((1...10).contains(11 ... 11)) + + XCTAssertFalse((1...10).contains(-1 ... 1)) + + } + + func testClosedRange_contains_Range() { + + XCTAssertFalse((1...10).contains( 1 ..< 1)) // empty, but in range + XCTAssertTrue ((1...10).contains( 1 ..< 10)) // 1...9 + XCTAssertTrue ((1...10).contains( 1 ..< 11)) // 1...10 + XCTAssertFalse((1...10).contains(10 ..< 10)) // empty + XCTAssertTrue ((1...10).contains(10 ..< 11)) // 10...10 + + XCTAssertFalse((1...10).contains( 0 ..< 0)) // empty, out of range + XCTAssertFalse((1...10).contains( 0 ..< 1)) // 0...0, out of range + XCTAssertFalse((1...10).contains( 0 ..< 2)) // 0...1, lowerBound out of range + XCTAssertFalse((1...10).contains( 0 ..< 10)) // 0...9, lowerBound out of range + XCTAssertTrue ((1...10).contains( 9 ..< 11)) // 9...10 + XCTAssertTrue ((1...10).contains(10 ..< 11)) // 10...10 + XCTAssertFalse((1...10).contains(11 ..< 11)) // empty, out of range + + XCTAssertFalse((1...10).contains(-1 ..< 1)) + + } + + func testRange_contains_ClosedRange() { + + XCTAssertTrue ((1..<10).contains( 1 ... 1)) + XCTAssertTrue ((1..<10).contains( 1 ... 9)) + XCTAssertFalse((1..<10).contains( 1 ... 10)) + XCTAssertFalse((1..<10).contains(10 ... 10)) + + XCTAssertFalse((1..<10).contains( 0 ... 0)) + XCTAssertFalse((1..<10).contains( 0 ... 1)) + XCTAssertFalse((1..<10).contains( 0 ... 2)) + XCTAssertFalse((1..<10).contains( 0 ... 9)) + XCTAssertFalse((1..<10).contains( 0 ... 10)) + XCTAssertFalse((1..<10).contains( 9 ... 11)) + XCTAssertFalse((1..<10).contains(10 ... 11)) + XCTAssertFalse((1..<10).contains(11 ... 11)) + + XCTAssertFalse((1..<10).contains(-1 ... 1)) + + } + + func testRange_contains_Range() { + + XCTAssertTrue ((1..<10).contains( 1 ..< 1)) // empty, but in range + XCTAssertTrue ((1..<10).contains( 1 ..< 10)) // identical + XCTAssertFalse((1..<10).contains( 1 ..< 11)) // 1...10 + XCTAssertTrue ((1..<10).contains(10 ..< 10)) // empty, but in range + XCTAssertFalse((1..<10).contains(10 ..< 11)) // 10...10 + + XCTAssertFalse((1..<10).contains( 0 ..< 0)) // empty, out of range + XCTAssertFalse((1..<10).contains( 0 ..< 1)) // 0...0, out of range + XCTAssertFalse((1..<10).contains( 0 ..< 2)) // 0...1, lowerBound out of range + XCTAssertFalse((1..<10).contains( 0 ..< 10)) // 0...9, lowerBound out of range + XCTAssertFalse((1..<10).contains( 9 ..< 11)) // 9...10 + XCTAssertFalse((1..<10).contains(10 ..< 11)) // 10...10, out of range + XCTAssertFalse((1..<10).contains(11 ..< 12)) // 11...11, out of range + XCTAssertFalse((1..<10).contains(11 ..< 11)) // empty, out of range + + XCTAssertFalse((1..<10).contains(-1 ..< 1)) + + // edge cases + + XCTAssertTrue ((Int.min..