diff --git a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/collections/SortedBinaryTreeNode.kt b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/collections/SortedBinaryTreeNode.kt index fade45c3e61..96a40bdcb85 100644 --- a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/collections/SortedBinaryTreeNode.kt +++ b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/collections/SortedBinaryTreeNode.kt @@ -1,7 +1,6 @@ package dev.inmo.micro_utils.coroutines.collections import dev.inmo.micro_utils.coroutines.SmartRWLocker -import dev.inmo.micro_utils.coroutines.waitReadRelease import dev.inmo.micro_utils.coroutines.withReadAcquire import dev.inmo.micro_utils.coroutines.withWriteLock import kotlinx.coroutines.job @@ -93,7 +92,7 @@ class SortedBinaryTreeNode( * This process will continue until function will not find place to put [SortedBinaryTreeNode] with data or * [SortedBinaryTreeNode] with [SortedBinaryTreeNode.data] same as [newData] will be found */ -private suspend fun SortedBinaryTreeNode.addSubNode( +private suspend fun SortedBinaryTreeNode.upsertSubNode( subNode: SortedBinaryTreeNode, skipLockers: Set = emptySet() ): SortedBinaryTreeNode { @@ -149,7 +148,7 @@ private suspend fun SortedBinaryTreeNode.addSubNode( * [SortedBinaryTreeNode] with [SortedBinaryTreeNode.data] same as [newData] will be found */ suspend fun SortedBinaryTreeNode.addSubNode(newData: T): SortedBinaryTreeNode { - return addSubNode( + return upsertSubNode( SortedBinaryTreeNode(newData, comparator) ) } @@ -198,8 +197,8 @@ suspend fun SortedBinaryTreeNode.findParentNode(data: T): SortedBinaryTre */ suspend fun SortedBinaryTreeNode.removeSubNode(data: T): Pair, SortedBinaryTreeNode>? { val onFoundToRemoveCallback: suspend SortedBinaryTreeNode.(left: SortedBinaryTreeNode?, right: SortedBinaryTreeNode?) -> Unit = { left, right -> - left ?.also { leftNode -> addSubNode(leftNode, setOf(locker)) } - right ?.also { rightNode -> addSubNode(rightNode, setOf(locker)) } + left ?.also { leftNode -> upsertSubNode(leftNode, setOf(locker)) } + right ?.also { rightNode -> upsertSubNode(rightNode, setOf(locker)) } } while (coroutineContext.job.isActive) { val foundParentNode = findParentNode(data) ?: return null 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 index 00efcf83917..c5d5c3d4be7 100644 --- 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 @@ -99,7 +99,7 @@ class SortedMapLikeBinaryTreeNode( * * @param replaceMode Will replace only value if node already exists */ -private suspend fun SortedMapLikeBinaryTreeNode.addSubNode( +private suspend fun SortedMapLikeBinaryTreeNode.upsertSubNode( subNode: SortedMapLikeBinaryTreeNode, skipLockers: Set = emptySet(), replaceMode: Boolean @@ -180,11 +180,11 @@ private suspend fun SortedMapLikeBinaryTreeNode.addSubNode( * 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( +suspend fun SortedMapLikeBinaryTreeNode.upsertSubNode( key: K, value: V ): SortedMapLikeBinaryTreeNode { - return addSubNode( + return upsertSubNode( SortedMapLikeBinaryTreeNode(key, value, comparator), replaceMode = false ) @@ -234,8 +234,8 @@ suspend fun SortedMapLikeBinaryTreeNode.findParentNode(data: K): So */ 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) } + left ?.also { leftNode -> upsertSubNode(leftNode, setOf(locker), replaceMode = true) } + right ?.also { rightNode -> upsertSubNode(rightNode, setOf(locker), replaceMode = true) } } while (coroutineContext.job.isActive) { val foundParentNode = findParentNode(data) ?: return null @@ -337,6 +337,52 @@ suspend fun SortedMapLikeBinaryTreeNode.findNodesInRange(from: K, t } error("Unable to find nodes range") } +suspend fun SortedMapLikeBinaryTreeNode.deepEquals(other: SortedMapLikeBinaryTreeNode): Boolean { + val leftToCheck = mutableSetOf(this) + val othersToCheck = mutableSetOf(other) + val lockedLockers = mutableSetOf() + try { + while (leftToCheck.isNotEmpty() && othersToCheck.isNotEmpty()) { + val thisToCheck = leftToCheck.first() + leftToCheck.remove(thisToCheck) + + val otherToCheck = othersToCheck.first() + othersToCheck.remove(otherToCheck) + + if (thisToCheck.locker !in lockedLockers) { + thisToCheck.locker.acquireRead() + lockedLockers.add(thisToCheck.locker) + } + if (otherToCheck.locker !in lockedLockers) { + otherToCheck.locker.acquireRead() + lockedLockers.add(otherToCheck.locker) + } + + if (thisToCheck.key != otherToCheck.key || thisToCheck.value != otherToCheck.value) { + return false + } + + if ((thisToCheck.leftNode == null).xor(otherToCheck.leftNode == null)) { + return false + } + if ((thisToCheck.rightNode == null).xor(otherToCheck.rightNode == null)) { + return false + } + + thisToCheck.leftNode?.let { leftToCheck.add(it) } + thisToCheck.rightNode?.let { leftToCheck.add(it) } + + otherToCheck.leftNode?.let { othersToCheck.add(it) } + otherToCheck.rightNode?.let { othersToCheck.add(it) } + } + } finally { + lockedLockers.forEach { + runCatching { it.releaseRead() } + } + } + + return leftToCheck.isEmpty() && othersToCheck.isEmpty() +} suspend fun SortedMapLikeBinaryTreeNode.findNodesInRange(from: K, to: K): Set> = findNodesInRange( from = from, to = to, diff --git a/coroutines/src/jvmTest/kotlin/dev/inmo/micro_utils/coroutines/SortedMapLikeBinaryTreeNodeTests.kt b/coroutines/src/jvmTest/kotlin/dev/inmo/micro_utils/coroutines/SortedMapLikeBinaryTreeNodeTests.kt new file mode 100644 index 00000000000..6b54b082ce2 --- /dev/null +++ b/coroutines/src/jvmTest/kotlin/dev/inmo/micro_utils/coroutines/SortedMapLikeBinaryTreeNodeTests.kt @@ -0,0 +1,132 @@ +package dev.inmo.micro_utils.coroutines + +import dev.inmo.micro_utils.coroutines.collections.* +import kotlinx.coroutines.test.runTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import kotlin.time.Duration.Companion.seconds + +class SortedMapLikeBinaryTreeNodeTests { + @Test + fun insertOnZeroLevelWorks() = runTest { + val zeroNode = SortedMapLikeBinaryTreeNode(0, 0) + zeroNode.upsertSubNode(1, 1) + zeroNode.upsertSubNode(-1, -1) + + assertEquals(0, zeroNode.key) + assertEquals(1, zeroNode.getRightNode() ?.key) + assertEquals(-1, zeroNode.getLeftNode() ?.key) + + assertEquals(0, zeroNode.findNode(0) ?.value) + assertEquals(1, zeroNode.findNode(1) ?.value) + assertEquals(-1, zeroNode.findNode(-1) ?.value) + } + @Test + fun searchOnZeroLevelWorks() = runTest { + val zeroNode = SortedMapLikeBinaryTreeNode(0, 0) + val oneNode = zeroNode.upsertSubNode(1, 1) + val minusOneNode = zeroNode.upsertSubNode(-1, -1) + + val assertingNodesToSearchQuery = mapOf( + setOf(oneNode) to (1 .. 1), + setOf(zeroNode, oneNode) to (0 .. 1), + setOf(minusOneNode, zeroNode, oneNode) to (-1 .. 1), + setOf(minusOneNode, zeroNode) to (-1 .. 0), + setOf(minusOneNode) to (-1 .. -1), + setOf(zeroNode) to (0 .. 0), + ) + + assertingNodesToSearchQuery.forEach { + val foundData = zeroNode.findNodesInRange(it.value) + assertTrue(foundData.containsAll(it.key)) + assertTrue(it.key.containsAll(foundData)) + } + } + @Test + fun deepReInsertOnWorks() = runTest(timeout = 300.seconds) { + var zeroNode = SortedMapLikeBinaryTreeNode(0, 0) + val rangeRadius = 500 + val nodes = mutableMapOf>() + for (i in -rangeRadius .. rangeRadius) { + nodes[i] = zeroNode.upsertSubNode(i, i) + if (i == zeroNode.key) { + zeroNode = nodes.getValue(i) + } + } + + for (i in -rangeRadius .. rangeRadius) { + val expectedNode = nodes.getValue(i) + val foundNode = zeroNode.findNode(i) + + assertEquals(expectedNode, foundNode) + + if (expectedNode === zeroNode) continue + + val parentNode = zeroNode.findParentNode(i) + assertTrue( + parentNode ?.getLeftNode() === expectedNode || parentNode ?.getRightNode() === expectedNode, + "It is expected, that parent node with data ${parentNode ?.key} will be parent of ${expectedNode.key}, but its left subnode is ${parentNode ?.getLeftNode() ?.key} and right one is ${parentNode ?.getRightNode() ?.key}" + ) + + zeroNode.upsertSubNode(i, -i) + val foundModifiedNode = zeroNode.findNode(i) + assertEquals(foundNode ?.value, foundModifiedNode ?.value ?.times(-1)) + assertTrue( + foundNode != null && foundModifiedNode != null && foundNode.deepEquals(foundModifiedNode) + ) + } + } +// @Test +// fun deepInsertOnWorks() = runTest(timeout = 240.seconds) { +// val zeroNode = SortedMapLikeBinaryTreeNode(0) +// val rangeRadius = 500 +// val nodes = mutableMapOf>() +// for (i in -rangeRadius .. rangeRadius) { +// nodes[i] = zeroNode.addSubNode(i) +// } +// +// for (i in -rangeRadius .. rangeRadius) { +// val expectedNode = nodes.getValue(i) +// val foundNode = zeroNode.findNode(i) +// +// assertTrue(expectedNode === foundNode) +// +// if (expectedNode === zeroNode) continue +// +// val parentNode = zeroNode.findParentNode(i) +// assertTrue( +// parentNode ?.getLeftNode() === expectedNode || parentNode ?.getRightNode() === expectedNode, +// "It is expected, that parent node with data ${parentNode ?.data} will be parent of ${expectedNode.data}, but its left subnode is ${parentNode ?.getLeftNode() ?.data} and right one is ${parentNode ?.getRightNode() ?.data}" +// ) +// } +// +// val sourceTreeSize = zeroNode.size() +// +// var previousData = -rangeRadius - 1 +// for (node in zeroNode) { +// assertTrue(nodes[node.data] === node) +// assertTrue(previousData == node.data - 1) +// previousData = node.data +// } +// +// assertTrue(sourceTreeSize == zeroNode.size()) +// } +// @Test +// fun deepInsertIteratorWorking() = runTest { +// val zeroNode = SortedMapLikeBinaryTreeNode(0) +// val rangeRadius = 500 +// val nodes = mutableMapOf>() +// for (i in -rangeRadius .. rangeRadius) { +// nodes[i] = zeroNode.addSubNode(i) +// } +// +// var previousData = -rangeRadius - 1 +// for (node in zeroNode) { +// assertTrue(nodes[node.data] === node) +// assertTrue(previousData == node.data - 1) +// previousData = node.data +// } +// assertTrue(previousData == rangeRadius) +// } +} \ No newline at end of file