Skip to content

Instantly share code, notes, and snippets.

@romiroma
Last active February 22, 2021 14:15
Show Gist options
  • Save romiroma/bb57ff9b3ce288c5c0055687b3a6a3c7 to your computer and use it in GitHub Desktop.
Save romiroma/bb57ff9b3ce288c5c0055687b3a6a3c7 to your computer and use it in GitHub Desktop.
NonContinuousRange - merging ClosedRange<T: Strideable>
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