|
| 1 | +@file:Suppress("PackageDirectoryMismatch") |
| 2 | + |
| 3 | +package korlibs.math.segment |
| 4 | + |
| 5 | +import korlibs.datastructure.BSearchResult |
| 6 | +import korlibs.datastructure.IntArrayList |
| 7 | +import korlibs.datastructure.genericBinarySearch |
| 8 | +import korlibs.math.annotations.* |
| 9 | +import kotlin.math.max |
| 10 | +import kotlin.math.min |
| 11 | + |
| 12 | +/** |
| 13 | + * Non-overlapping SegmentSet |
| 14 | + */ |
| 15 | +@KormaExperimental |
| 16 | +class IntSegmentSet { |
| 17 | + @PublishedApi |
| 18 | + internal val min = IntArrayList(16) |
| 19 | + @PublishedApi |
| 20 | + internal val max = IntArrayList(16) |
| 21 | + val size get() = min.size |
| 22 | + fun isEmpty() = size == 0 |
| 23 | + fun isNotEmpty() = size > 0 |
| 24 | + |
| 25 | + fun clear() = this.apply { |
| 26 | + min.clear() |
| 27 | + max.clear() |
| 28 | + } |
| 29 | + |
| 30 | + fun copyFrom(other: IntSegmentSet) = this.apply { |
| 31 | + this.clear() |
| 32 | + addUnsafe(other) |
| 33 | + } |
| 34 | + |
| 35 | + fun clone() = IntSegmentSet().copyFrom(this) |
| 36 | + |
| 37 | + val minMin get() = if (isNotEmpty()) min.getAt(0) else 0 |
| 38 | + val maxMax get() = if (isNotEmpty()) max.getAt(max.size - 1) else 0 |
| 39 | + |
| 40 | + fun findNearIndex(x: Int): BSearchResult = BSearchResult(genericBinarySearch(0, size) { v -> |
| 41 | + val min = this.min.getAt(v) |
| 42 | + val max = this.max.getAt(v) |
| 43 | + when { |
| 44 | + x < min -> +1 |
| 45 | + x > max -> -1 |
| 46 | + else -> 0 |
| 47 | + } |
| 48 | + }) |
| 49 | + |
| 50 | + inline fun fastForEach(block: (n: Int, min: Int, max: Int) -> Unit) { |
| 51 | + for (n in 0 until size) block(n, min.getAt(n), max.getAt(n)) |
| 52 | + } |
| 53 | + |
| 54 | + fun findLeftBound(x: Int): Int { |
| 55 | + //if (size < 8) return 0 // Do not invest time on binary search on small sets |
| 56 | + return (genericBinarySearchLeft(0, size) { this.min.getAt(it).compareTo(x) }).coerceIn(0, size - 1) |
| 57 | + } |
| 58 | + fun findRightBound(x: Int): Int { |
| 59 | + //if (size < 8) return size - 1 // Do not invest time on binary search on small sets |
| 60 | + return (genericBinarySearchRight(0, size) { this.max.getAt(it).compareTo(x) }).coerceIn(0, size - 1) |
| 61 | + } |
| 62 | + |
| 63 | + inline fun fastForEachInterestingRange(min: Int, max: Int, block: (n: Int, x1: Int, x2: Int) -> Unit) { |
| 64 | + if (isEmpty()) return |
| 65 | + val nmin = findLeftBound(min) |
| 66 | + val nmax = findRightBound(max) |
| 67 | + for (n in nmin..nmax) block(n, this.min.getAt(n), this.max.getAt(n)) |
| 68 | + } |
| 69 | + |
| 70 | + internal fun addUnsafe(min: Int, max: Int) = this.apply { |
| 71 | + check(min <= max) |
| 72 | + insertAt(size, min, max) |
| 73 | + } |
| 74 | + |
| 75 | + internal fun addUnsafe(other: IntSegmentSet) = this.apply { |
| 76 | + this.min.add(other.min) |
| 77 | + this.max.add(other.max) |
| 78 | + } |
| 79 | + |
| 80 | + fun add(other: IntSegmentSet) = this.apply { |
| 81 | + other.fastForEach { n, min, max -> |
| 82 | + add(min, max) |
| 83 | + } |
| 84 | + } |
| 85 | + |
| 86 | + fun add(min: Int, max: Int) = this.apply { |
| 87 | + check(min <= max) |
| 88 | + when { |
| 89 | + isEmpty() -> insertAt(size, min, max) |
| 90 | + min == maxMax -> this.max[this.max.size - 1] = max |
| 91 | + max == minMin -> this.min[0] = min |
| 92 | + else -> { |
| 93 | + var removeStart = -1 |
| 94 | + var removeCount = -1 |
| 95 | + |
| 96 | + fastForEachInterestingRange(min, max) { n, x1, x2 -> |
| 97 | + if (intersects(x1, x2, min, max)) { |
| 98 | + if (removeStart == -1) removeStart = n |
| 99 | + this.min[removeStart] = min(this.min.getAt(removeStart), min(x1, min)) |
| 100 | + this.max[removeStart] = max(this.max.getAt(removeStart), max(x2, max)) |
| 101 | + removeCount++ |
| 102 | + } |
| 103 | + } |
| 104 | + |
| 105 | + when { |
| 106 | + // Combined |
| 107 | + removeCount == 0 -> Unit |
| 108 | + removeCount > 0 -> removeAt(removeStart + 1, removeCount) |
| 109 | + // Insert at the beginning |
| 110 | + max < minMin -> insertAt(0, min, max) |
| 111 | + // Insert at the end |
| 112 | + min > maxMax -> insertAt(size, min, max) |
| 113 | + // Insert at a place |
| 114 | + else -> { |
| 115 | + for (m in findLeftBound(min).coerceAtLeast(1)..findRightBound(max)) { |
| 116 | + //for (m in 1..findRightBound(max)) { |
| 117 | + val prevMax = this.max.getAt(m - 1) |
| 118 | + val currMin = this.min.getAt(m) |
| 119 | + if (min > prevMax && max < currMin) { |
| 120 | + insertAt(m, min, max) |
| 121 | + return@apply |
| 122 | + } |
| 123 | + } |
| 124 | + |
| 125 | + error("Unexpected") |
| 126 | + } |
| 127 | + } |
| 128 | + } |
| 129 | + } |
| 130 | + } |
| 131 | + |
| 132 | + private fun insertAt(n: Int, min: Int, max: Int) { |
| 133 | + this.min.insertAt(n, min) |
| 134 | + this.max.insertAt(n, max) |
| 135 | + } |
| 136 | + |
| 137 | + private fun removeAt(n: Int, count: Int) { |
| 138 | + this.min.removeAt(n, count) |
| 139 | + this.max.removeAt(n, count) |
| 140 | + } |
| 141 | + |
| 142 | + //fun remove(min: Int, max: Int) = this.apply { TODO() } |
| 143 | + //fun intersect(min: Int, max: Int) = this.apply { TODO() } |
| 144 | + |
| 145 | + inline fun intersection(min: Int, max: Int, out: (min: Int, max: Int) -> Unit): Boolean { |
| 146 | + var count = 0 |
| 147 | + fastForEachInterestingRange(min, max) { n, x1, x2 -> |
| 148 | + if (intersects(x1, x2, min, max)) { |
| 149 | + out(max(x1, min), min(x2, max)) |
| 150 | + count++ |
| 151 | + } |
| 152 | + } |
| 153 | + |
| 154 | + return count > 0 |
| 155 | + } |
| 156 | + |
| 157 | + // Use for testing |
| 158 | + // O(n * log(n)) |
| 159 | + internal fun intersectionSlow(min: Int, max: Int): Pair<Int, Int>? { |
| 160 | + var out: Pair<Int, Int>? = null |
| 161 | + intersectionSlow(min, max) { x1, x2 -> out = x1 to x2 } |
| 162 | + return out |
| 163 | + } |
| 164 | + |
| 165 | + // Use for testing |
| 166 | + // O(n^2) |
| 167 | + internal inline fun intersectionSlow(min: Int, max: Int, out: (min: Int, max: Int) -> Unit): Boolean { |
| 168 | + var count = 0 |
| 169 | + fastForEach { n, x1, x2 -> |
| 170 | + if (intersects(x1, x2, min, max)) { |
| 171 | + out(max(x1, min), min(x2, max)) |
| 172 | + count++ |
| 173 | + } |
| 174 | + } |
| 175 | + return count > 0 |
| 176 | + } |
| 177 | + |
| 178 | + operator fun contains(v: Int): Boolean = findNearIndex(v).found |
| 179 | + |
| 180 | + fun setToIntersect(a: IntSegmentSet, b: IntSegmentSet) = this.apply { |
| 181 | + val aSmaller = a.size < b.size |
| 182 | + val av = if (aSmaller) a else b |
| 183 | + val bv = if (aSmaller) b else a |
| 184 | + clear().also { av.fastForEach { n, x1, x2 -> bv.intersection(x1, x2) { min, max -> add(min, max) } } } |
| 185 | + } |
| 186 | + |
| 187 | + // Use for testing |
| 188 | + internal fun setToIntersectSlow(a: IntSegmentSet, b: IntSegmentSet) = this.apply { |
| 189 | + clear().also { a.fastForEach { n, x1, x2 -> b.intersectionSlow(x1, x2) { min, max -> add(min, max) } } } |
| 190 | + } |
| 191 | + |
| 192 | + @PublishedApi |
| 193 | + internal fun intersects(x1: Int, x2: Int, y1: Int, y2: Int): Boolean = x2 >= y1 && y2 >= x1 |
| 194 | + @PublishedApi |
| 195 | + internal fun intersects(x1: Int, x2: Int, index: Int): Boolean = intersects(x1, x2, min.getAt(index), max.getAt(index)) |
| 196 | + |
| 197 | + @PublishedApi |
| 198 | + internal fun contains(v: Int, x1: Int, x2: Int): Boolean = v in x1 until x2 |
| 199 | + @PublishedApi |
| 200 | + internal fun contains(v: Int, index: Int): Boolean = contains(v, min.getAt(index), max.getAt(index)) |
| 201 | + |
| 202 | + override fun toString(): String = buildString { |
| 203 | + append("[") |
| 204 | + fastForEach { n, min, max -> |
| 205 | + val first = (n == 0) |
| 206 | + if (!first) append(", ") |
| 207 | + append("$min-$max") |
| 208 | + } |
| 209 | + append("]") |
| 210 | + } |
| 211 | +} |
| 212 | + |
| 213 | +// @TODO: In KDS latest versions |
| 214 | +@PublishedApi |
| 215 | +internal inline fun genericBinarySearchLeft(fromIndex: Int, toIndex: Int, check: (value: Int) -> Int): Int = |
| 216 | + genericBinarySearch(fromIndex, toIndex, invalid = { from, to, low, high -> min(low, high).coerceIn(from, to - 1) }, check = check) |
| 217 | +@PublishedApi |
| 218 | +internal inline fun genericBinarySearchRight(fromIndex: Int, toIndex: Int, check: (value: Int) -> Int): Int = |
| 219 | + genericBinarySearch(fromIndex, toIndex, invalid = { from, to, low, high -> max(low, high).coerceIn(from, to - 1) }, check = check) |
0 commit comments