From b343b33594436e18308377e1688f25978f605a59 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 5 Mar 2025 16:23:39 +0600 Subject: [PATCH] buildable variant of map-like sorted tree --- .../SortedMapLikeBinaryTreeNode.kt | 355 ++++++++++++++++++ 1 file changed, 355 insertions(+) create mode 100644 coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/collections/SortedMapLikeBinaryTreeNode.kt diff --git a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/collections/SortedMapLikeBinaryTreeNode.kt b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/collections/SortedMapLikeBinaryTreeNode.kt new file mode 100644 index 00000000000..00efcf83917 --- /dev/null +++ b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/collections/SortedMapLikeBinaryTreeNode.kt @@ -0,0 +1,355 @@ +package dev.inmo.micro_utils.coroutines.collections + +import dev.inmo.micro_utils.coroutines.SmartRWLocker +import dev.inmo.micro_utils.coroutines.withReadAcquire +import dev.inmo.micro_utils.coroutines.withWriteLock +import kotlinx.coroutines.job +import kotlinx.serialization.Serializable +import kotlin.coroutines.coroutineContext + +/** + * Creates simple [Comparator] which will use [compareTo] of [T] for both objects + */ +private fun , C : T> T.createComparator() = Comparator { o1, o2 -> o1.compareTo(o2) } + +@Serializable +class SortedMapLikeBinaryTreeNode( + val key: K, + val value: V, + internal val comparator: Comparator, +) : Iterable> { + internal var leftNode: SortedMapLikeBinaryTreeNode? = null + internal var rightNode: SortedMapLikeBinaryTreeNode? = null + internal val locker: SmartRWLocker by lazy { + SmartRWLocker() + } + + suspend fun getLeftNode() = locker.withReadAcquire { + leftNode + } + + suspend fun getRightNode() = locker.withReadAcquire { + rightNode + } + + suspend fun getLeftKey() = getLeftNode() ?.key + suspend fun getLeftValue() = getLeftNode() ?.value + + suspend fun getRightKey() = getRightNode() ?.value + suspend fun getRightValue() = getRightNode() ?.value + + override fun equals(other: Any?): Boolean { + return other === this || (other is SortedMapLikeBinaryTreeNode<*, *> && other.key == key && other.rightNode == rightNode && other.leftNode == leftNode) + } + + override fun hashCode(): Int { + return key.hashCode() * 31 + rightNode.hashCode() + leftNode.hashCode() + } + + suspend fun size(): Int { + return locker.withReadAcquire { + 1 + (leftNode ?.size() ?: 0) + (rightNode ?.size() ?: 0) + } + } + + /** + * This [Iterator] will run from less to greater values of nodes starting the + * [dev.inmo.micro_utils.coroutines.collections.SortedMapLikeBinaryTreeNode]-receiver. Due to non-suspending + * nature of [iterator] builder, this [Iterator] **DO NOT** guarantee consistent content due to iterations. It + * means, that tree can be changed during to iteration process + */ + override fun iterator(): Iterator> = iterator { + leftNode ?.let { + it.iterator().forEach { yield(it) } + } + yield(this@SortedMapLikeBinaryTreeNode) + rightNode ?.let { + it.iterator().forEach { yield(it) } + } + } + + override fun toString(): String { + return "$key($leftNode;$rightNode)" + } + + companion object { + operator fun , V> invoke( + key: K, + value: V + ) = SortedMapLikeBinaryTreeNode( + key, + value, + key.createComparator() + ) + } +} + +/** + * Will add subnode in tree if there are no any node with [newData] + * + * * If [newData] is greater than [SortedMapLikeBinaryTreeNode.key] of currently checking node, + * will be used [SortedMapLikeBinaryTreeNode.rightNode] + * * If [newData] is equal to [SortedMapLikeBinaryTreeNode.key] of currently + * checking node - will be returned currently checking node + * * If [newData] is less than [SortedMapLikeBinaryTreeNode.key] of currently + * checking node - will be used [SortedMapLikeBinaryTreeNode.leftNode] + * + * This process will continue until function will not find place to put [SortedMapLikeBinaryTreeNode] with data or + * [SortedMapLikeBinaryTreeNode] with [SortedMapLikeBinaryTreeNode.key] same as [newData] will be found + * + * @param replaceMode Will replace only value if node already exists + */ +private suspend fun SortedMapLikeBinaryTreeNode.addSubNode( + subNode: SortedMapLikeBinaryTreeNode, + skipLockers: Set = emptySet(), + replaceMode: Boolean +): SortedMapLikeBinaryTreeNode { + var currentlyChecking = this + var latestParent: SortedMapLikeBinaryTreeNode? = null + val lockedLockers = mutableSetOf() + try { + while (coroutineContext.job.isActive) { + if (currentlyChecking.locker !in lockedLockers && currentlyChecking.locker !in skipLockers) { + currentlyChecking.locker.lockWrite() + lockedLockers.add(currentlyChecking.locker) + } + val left = currentlyChecking.leftNode + val right = currentlyChecking.rightNode + val comparingResult = currentlyChecking.comparator.compare(subNode.key, currentlyChecking.key) + val isGreater = comparingResult > 0 + when { + comparingResult == 0 -> { + val resultNode = if (replaceMode) { + subNode + } else { + val newNode = SortedMapLikeBinaryTreeNode( + subNode.key, + subNode.value, + currentlyChecking.comparator, + ) + newNode.leftNode = currentlyChecking.leftNode + newNode.rightNode = currentlyChecking.rightNode + newNode + } + + latestParent ?.let { + when { + it.leftNode === currentlyChecking -> it.leftNode = resultNode + it.rightNode === currentlyChecking -> it.rightNode = resultNode + } + } + + return resultNode + } + isGreater && right == null -> { + currentlyChecking.rightNode = subNode + return subNode + } + isGreater && right != null -> { + latestParent = currentlyChecking + currentlyChecking = right + } + left == null -> { + currentlyChecking.leftNode = subNode + return subNode + } + else -> { + latestParent = currentlyChecking + currentlyChecking = left + } + } + } + } finally { + lockedLockers.forEach { + runCatching { it.unlockWrite() } + } + } + error("Unable to add node") +} + +/** + * Will add subnode in tree if there are no any node with [key] + * + * * If [key] is greater than [SortedMapLikeBinaryTreeNode.key] of currently checking node, + * will be used [SortedMapLikeBinaryTreeNode.rightNode] + * * If [key] is equal to [SortedMapLikeBinaryTreeNode.key] of currently + * checking node - will be returned currently checking node + * * If [key] is less than [SortedMapLikeBinaryTreeNode.key] of currently + * checking node - will be used [SortedMapLikeBinaryTreeNode.leftNode] + * + * This process will continue until function will not find place to put [SortedMapLikeBinaryTreeNode] with data or + * [SortedMapLikeBinaryTreeNode] with [SortedMapLikeBinaryTreeNode.key] same as [key] will be found + */ +suspend fun SortedMapLikeBinaryTreeNode.addSubNode( + key: K, + value: V +): SortedMapLikeBinaryTreeNode { + return addSubNode( + SortedMapLikeBinaryTreeNode(key, value, comparator), + replaceMode = false + ) +} + +suspend fun SortedMapLikeBinaryTreeNode.findParentNode(data: K): SortedMapLikeBinaryTreeNode? { + var currentParent: SortedMapLikeBinaryTreeNode? = null + var currentlyChecking: SortedMapLikeBinaryTreeNode? = this + val lockedLockers = mutableSetOf() + try { + while (coroutineContext.job.isActive) { + if (currentlyChecking == null) { + return null + } + if (currentlyChecking.locker !in lockedLockers) { + currentlyChecking.locker.acquireRead() + lockedLockers.add(currentlyChecking.locker) + } + val comparingResult = currentlyChecking.comparator.compare(data, currentlyChecking.key) + when { + comparingResult > 0 -> { + currentParent = currentlyChecking + currentlyChecking = currentlyChecking.rightNode + continue + } + comparingResult < 0 -> { + currentParent = currentlyChecking + currentlyChecking = currentlyChecking.leftNode + continue + } + else -> return currentParent + } + } + } finally { + lockedLockers.forEach { + runCatching { it.releaseRead() } + } + } + error("Unable to find node") +} + +/** + * Will remove (detach) node from tree starting with [this] [SortedMapLikeBinaryTreeNode] + * + * @return If data were found, [Pair] where [Pair.first] is the parent node where from [Pair.second] has been detached; + * null otherwise + */ +suspend fun SortedMapLikeBinaryTreeNode.removeSubNode(data: K): Pair, SortedMapLikeBinaryTreeNode>? { + val onFoundToRemoveCallback: suspend SortedMapLikeBinaryTreeNode.(left: SortedMapLikeBinaryTreeNode?, right: SortedMapLikeBinaryTreeNode?) -> Unit = { left, right -> + left ?.also { leftNode -> addSubNode(leftNode, setOf(locker), replaceMode = true) } + right ?.also { rightNode -> addSubNode(rightNode, setOf(locker), replaceMode = true) } + } + while (coroutineContext.job.isActive) { + val foundParentNode = findParentNode(data) ?: return null + foundParentNode.locker.withWriteLock { + val left = foundParentNode.leftNode + val right = foundParentNode.rightNode + when { + left != null && left.comparator.compare(data, left.key) == 0 -> { + foundParentNode.leftNode = null + foundParentNode.onFoundToRemoveCallback(left.leftNode, left.rightNode) + return foundParentNode to left + } + right != null && right.comparator.compare(data, right.key) == 0 -> { + foundParentNode.rightNode = null + foundParentNode.onFoundToRemoveCallback(right.leftNode, right.rightNode) + return foundParentNode to right + } + else -> { + return@withWriteLock // data has been changed, new search required + } + } + } + } + error("Unable to remove node") +} +suspend fun SortedMapLikeBinaryTreeNode.findNode(key: K): SortedMapLikeBinaryTreeNode? { + var currentlyChecking: SortedMapLikeBinaryTreeNode? = this + val lockedLockers = mutableSetOf() + try { + while (coroutineContext.job.isActive) { + if (currentlyChecking == null) { + return null + } + if (currentlyChecking.locker !in lockedLockers) { + currentlyChecking.locker.acquireRead() + lockedLockers.add(currentlyChecking.locker) + } + val comparingResult = currentlyChecking.comparator.compare(key, currentlyChecking.key) + when { + comparingResult > 0 -> { + currentlyChecking = currentlyChecking.rightNode + continue + } + comparingResult < 0 -> { + currentlyChecking = currentlyChecking.leftNode + continue + } + else -> return currentlyChecking + } + } + } finally { + lockedLockers.forEach { + runCatching { it.releaseRead() } + } + } + error("Unable to find node") +} +suspend fun SortedMapLikeBinaryTreeNode.contains(data: K): Boolean = findNode(data) != null + +suspend fun SortedMapLikeBinaryTreeNode.findNodesInRange(from: K, to: K, fromInclusiveMode: Boolean, toInclusiveMode: Boolean): Set> { + val results = mutableSetOf>() + val leftToCheck = mutableSetOf(this) + val lockedLockers = mutableSetOf() + val fromComparingFun: (SortedMapLikeBinaryTreeNode) -> Boolean = if (fromInclusiveMode) { + { it.comparator.compare(from, it.key) <= 0 } + } else { + { it.comparator.compare(from, it.key) < 0 } + } + val toComparingFun: (SortedMapLikeBinaryTreeNode) -> Boolean = if (toInclusiveMode) { + { it.comparator.compare(to, it.key) >= 0 } + } else { + { it.comparator.compare(to, it.key) > 0 } + } + try { + while (coroutineContext.job.isActive && leftToCheck.isNotEmpty()) { + val currentlyChecking = leftToCheck.first() + leftToCheck.remove(currentlyChecking) + if (currentlyChecking in results) { + continue + } + currentlyChecking.locker.acquireRead() + lockedLockers.add(currentlyChecking.locker) + if (fromComparingFun(currentlyChecking) && toComparingFun(currentlyChecking)) { + results.add(currentlyChecking) + currentlyChecking.leftNode ?.let { leftToCheck.add(it) } + currentlyChecking.rightNode ?.let { leftToCheck.add(it) } + continue + } + when { + currentlyChecking.comparator.compare(to, currentlyChecking.key) < 0 -> currentlyChecking.leftNode ?.let { leftToCheck.add(it) } + currentlyChecking.comparator.compare(from, currentlyChecking.key) > 0 -> currentlyChecking.rightNode ?.let { leftToCheck.add(it) } + } + } + return results.toSet() + } finally { + lockedLockers.forEach { + runCatching { it.releaseRead() } + } + } + error("Unable to find nodes range") +} +suspend fun SortedMapLikeBinaryTreeNode.findNodesInRange(from: K, to: K): Set> = findNodesInRange( + from = from, + to = to, + fromInclusiveMode = true, + toInclusiveMode = true +) +suspend fun SortedMapLikeBinaryTreeNode.findNodesInRangeExcluding(from: K, to: K): Set> = findNodesInRange( + from = from, + to = to, + fromInclusiveMode = false, + toInclusiveMode = false +) +suspend fun , V> SortedMapLikeBinaryTreeNode.findNodesInRange(range: ClosedRange): Set> = findNodesInRange( + from = range.start, + to = range.endInclusive, +) \ No newline at end of file