Last active
February 22, 2021 14:15
-
-
Save romiroma/bb57ff9b3ce288c5c0055687b3a6a3c7 to your computer and use it in GitHub Desktop.
NonContinuousRange - merging ClosedRange<T: Strideable>
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
struct NonContinuousRange<T: Strideable> where T.Stride: SignedInteger { | |
private(set) var ranges: [ClosedRange<T>] = [] | |
mutating func insert(range newRange: ClosedRange<T>) { | |
guard !ranges.isEmpty else { | |
ranges.append(newRange) | |
return | |
} | |
var insertionIndex: Int! = nil | |
for i in 0..<ranges.count where newRange.lowerBound <= ranges[i].lowerBound{ | |
ranges.insert(newRange, at: i) | |
insertionIndex = i | |
break | |
} | |
if insertionIndex == nil { | |
insertionIndex = ranges.count | |
ranges.append(newRange) | |
} | |
// we need to start our merge from index before insertion | |
let startingIndex = max(0, insertionIndex - 1) | |
var index = startingIndex | |
for i in (startingIndex + 1)..<ranges.count { | |
if ranges[index].upperBound >= ranges[i].lowerBound.advanced(by: -1) { | |
ranges[index] = .init(uncheckedBounds: ( | |
lower: Swift.min(ranges[index].lowerBound, ranges[i].lowerBound), | |
upper: Swift.max(ranges[index].upperBound, ranges[i].upperBound) | |
)) | |
} else { | |
index += 1 | |
ranges[index] = ranges[i] | |
} | |
} | |
ranges.removeLast(ranges.count - index - 1) | |
} | |
} | |
extension NonContinuousRange { | |
func contains(range: ClosedRange<T>) -> Bool { | |
for r in ranges where r ~= range.lowerBound && r ~= range.upperBound { | |
return true | |
} | |
return false | |
} | |
func intersection(with range: ClosedRange<T>) -> Self { | |
ranges | |
.lazy | |
.filter({ $0.overlaps(range) }) | |
.reduce(into: NonContinuousRange<T>()) { | |
$0.insert(range: $1.clamped(to: range)) | |
} | |
} | |
func difference(from range: ClosedRange<T>) -> Self { | |
let intersection = self.intersection(with: range) | |
var difference = NonContinuousRange<T>() | |
if intersection.ranges.isEmpty { | |
difference.insert(range: range) | |
} else { | |
var lowerBound = range.lowerBound | |
for r in intersection.ranges { | |
if r.lowerBound > lowerBound { | |
difference.insert(range: lowerBound...r.lowerBound.advanced(by: -1)) | |
} | |
if r.upperBound < range.upperBound { | |
lowerBound = r.upperBound.advanced(by: 1) | |
} else { | |
break | |
} | |
} | |
let lastIntersectionRange = intersection.ranges.last! | |
if lastIntersectionRange.upperBound < range.upperBound { | |
difference.insert(range: lastIntersectionRange.upperBound.advanced(by: 1)...range.upperBound) | |
} | |
} | |
return difference | |
} | |
} | |
// MARK: Usage | |
var range: NonContinuousRange<Int> = .init() | |
range.insert(range: 50...100) | |
range.insert(range: 0...1) | |
range.insert(range: 3...4) | |
range.insert(range: 30...40) | |
range.insert(range: 2...5) | |
range.insert(range: 7...8) | |
range.insert(range: 10...29) | |
range.insert(range: 6...9) | |
range.insert(range: 41...49) | |
print(range.ranges) | |
// Prints [ClosedRange(0...100)] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment