Skip to content

Instantly share code, notes, and snippets.

@khanlou
Last active April 25, 2020 09:25
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save khanlou/bedf87d7ec871ce981b41a3151890aca to your computer and use it in GitHub Desktop.
Save khanlou/bedf87d7ec871ce981b41a3151890aca to your computer and use it in GitHub Desktop.
public struct ReversibleRange<Bound>: Equatable where Bound: Comparable {
public let startingBound: Bound
public let endingBound: Bound
public init(startingBound: Bound, endingBound: Bound) {
self.startingBound = startingBound
self.endingBound = endingBound
}
public var isReversed: Bool {
return startingBound > endingBound
}
public var minBound: Bound {
return Swift.min(endingBound, startingBound)
}
public var maxBound: Bound {
return Swift.max(startingBound, endingBound)
}
public var normalizedRange: ClosedRange<Bound> {
return minBound...maxBound
}
public func contains(_ element: Bound) -> Bool {
return normalizedRange.contains(element)
}
public var isEmpty: Bool {
return startingBound == endingBound
}
public func overlaps(_ other: ReversibleRange<Bound>) -> Bool {
return self.normalizedRange.overlaps(other.normalizedRange)
}
}
extension ReversibleRange: Sequence where Bound: Strideable {
public typealias Element = Bound
public typealias Iterator = AnyIterator<Bound>
public func makeIterator() -> AnyIterator<Bound> {
var current = startingBound
var step: Bound.Stride = isReversed ? -1 : 1
return AnyIterator({
if !self.isReversed && current > self.endingBound { return nil }
if self.isReversed && current < self.endingBound { return nil }
defer { current = current.advanced(by: step) }
return current
})
}
}
import XCTest
import ReversibleRange
class ReversibleRangeTests: XCTestCase {
let normalOrder = ReversibleRange(startingBound: 10, endingBound: 20)
let emptyRange = ReversibleRange(startingBound: 10, endingBound: 10)
let reversedOrder = ReversibleRange(startingBound: 20, endingBound: 10)
func testBasic() {
XCTAssertFalse(normalOrder.isReversed)
XCTAssert(reversedOrder.isReversed)
}
func testContains() {
XCTAssert(normalOrder.contains(15))
XCTAssert(reversedOrder.contains(15))
XCTAssertFalse(normalOrder.contains(21))
XCTAssertFalse(reversedOrder.contains(21))
}
func testEmpty() {
XCTAssertFalse(normalOrder.isEmpty)
XCTAssert(emptyRange.isEmpty)
}
func testSequence() {
XCTAssertEqual([3, 2, 1], Array(ReversibleRange(startingBound: 3, endingBound: 1)))
}
func testMinMaxBounds() {
XCTAssertEqual(normalOrder.minBound, 10)
XCTAssertEqual(normalOrder.maxBound, 20)
XCTAssertEqual(reversedOrder.minBound, 10)
XCTAssertEqual(reversedOrder.maxBound, 20)
}
func testNormalizedRange() {
XCTAssertEqual(normalOrder.normalizedRange, 10...20)
XCTAssertEqual(reversedOrder.normalizedRange, 10...20)
}
func testOverlaps() {
XCTAssert(ReversibleRange(startingBound: 10, endingBound: 20).overlaps(ReversibleRange(startingBound: 19, endingBound: 21)))
XCTAssert(ReversibleRange(startingBound: 20, endingBound: 10).overlaps(ReversibleRange(startingBound: 19, endingBound: 21)))
XCTAssert(ReversibleRange(startingBound: 20, endingBound: 10).overlaps(ReversibleRange(startingBound: 21, endingBound: 19)))
XCTAssert(ReversibleRange(startingBound: 10, endingBound: 20).overlaps(ReversibleRange(startingBound: 21, endingBound: 19)))
XCTAssertFalse(ReversibleRange(startingBound: 10, endingBound: 20).overlaps(ReversibleRange(startingBound: 21, endingBound: 23)))
XCTAssertFalse(ReversibleRange(startingBound: 20, endingBound: 10).overlaps(ReversibleRange(startingBound: 21, endingBound: 23)))
XCTAssertFalse(ReversibleRange(startingBound: 20, endingBound: 10).overlaps(ReversibleRange(startingBound: 23, endingBound: 21)))
XCTAssertFalse(ReversibleRange(startingBound: 10, endingBound: 20).overlaps(ReversibleRange(startingBound: 23, endingBound: 21)))
}
func testEquatable() {
XCTAssertEqual(ReversibleRange(startingBound: 10, endingBound: 20), ReversibleRange(startingBound: 10, endingBound: 20))
XCTAssertNotEqual(ReversibleRange(startingBound: 10, endingBound: 20), ReversibleRange(startingBound: 11, endingBound: 20))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment