From 531adfcda05bead8c174cc35eacd33c3c389da94 Mon Sep 17 00:00:00 2001 From: TheFollan Date: Sat, 27 Sep 2025 20:08:13 +0300 Subject: [PATCH 01/12] Create TwoThreeNode --- lib/src/main/kotlin/monke/nodes/TwoThreeTreeNode.kt | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 lib/src/main/kotlin/monke/nodes/TwoThreeTreeNode.kt diff --git a/lib/src/main/kotlin/monke/nodes/TwoThreeTreeNode.kt b/lib/src/main/kotlin/monke/nodes/TwoThreeTreeNode.kt new file mode 100644 index 0000000..5c48b62 --- /dev/null +++ b/lib/src/main/kotlin/monke/nodes/TwoThreeTreeNode.kt @@ -0,0 +1,6 @@ +package monke.nodes + +public class TwoThreeTreeNode, V> ( + key : K, + value : V +): BinaryTreeNode> (key, value) From 1e667b44a28d62ee8de337f1f156d1b8130ca3ed Mon Sep 17 00:00:00 2001 From: TheFollan Date: Sat, 27 Sep 2025 23:07:42 +0300 Subject: [PATCH 02/12] Added fun get --- lib/src/main/kotlin/monke/nodes/TwoThreeTreeNode.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/src/main/kotlin/monke/nodes/TwoThreeTreeNode.kt b/lib/src/main/kotlin/monke/nodes/TwoThreeTreeNode.kt index 5c48b62..aff39c2 100644 --- a/lib/src/main/kotlin/monke/nodes/TwoThreeTreeNode.kt +++ b/lib/src/main/kotlin/monke/nodes/TwoThreeTreeNode.kt @@ -1,6 +1,11 @@ package monke.nodes +data class Entry, V>( + val key: K, + var value: V +) public class TwoThreeTreeNode, V> ( - key : K, - value : V -): BinaryTreeNode> (key, value) + val entries: MutableList> = mutableListOf(), + val children: MutableList> = mutableListOf(), + var paretn: TwoThreeTreeNode? = null +) From 4c8e80e927dc79e5c13eb5d6b8148e2fbc7d2bc8 Mon Sep 17 00:00:00 2001 From: TheFollan Date: Sun, 28 Sep 2025 01:12:08 +0300 Subject: [PATCH 03/12] Added method insert --- lib/src/main/kotlin/monke/nodes/TwoThreeTreeNode.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/src/main/kotlin/monke/nodes/TwoThreeTreeNode.kt b/lib/src/main/kotlin/monke/nodes/TwoThreeTreeNode.kt index aff39c2..4d21426 100644 --- a/lib/src/main/kotlin/monke/nodes/TwoThreeTreeNode.kt +++ b/lib/src/main/kotlin/monke/nodes/TwoThreeTreeNode.kt @@ -8,4 +8,8 @@ public class TwoThreeTreeNode, V> ( val entries: MutableList> = mutableListOf(), val children: MutableList> = mutableListOf(), var paretn: TwoThreeTreeNode? = null -) +) { + val isLeaf : Boolean + get() = children.isEmpty() +} + From 354cd6d90c306bc690c2d1a874b755122a8c2edd Mon Sep 17 00:00:00 2001 From: TheFollan Date: Mon, 29 Sep 2025 17:31:42 +0300 Subject: [PATCH 04/12] Completed TwoThreeTree and test --- .../main/kotlin/monke/trees/TwoThreeTree.kt | 261 ++++++++++++++++++ .../monke/trees/treeInterfaces/BTree.kt | 10 + .../kotlin/monke/trees/TwoThreeTreeTest.kt | 79 ++++++ 3 files changed, 350 insertions(+) create mode 100644 lib/src/main/kotlin/monke/trees/TwoThreeTree.kt create mode 100644 lib/src/main/kotlin/monke/trees/treeInterfaces/BTree.kt create mode 100644 lib/src/test/kotlin/monke/trees/TwoThreeTreeTest.kt diff --git a/lib/src/main/kotlin/monke/trees/TwoThreeTree.kt b/lib/src/main/kotlin/monke/trees/TwoThreeTree.kt new file mode 100644 index 0000000..0043556 --- /dev/null +++ b/lib/src/main/kotlin/monke/trees/TwoThreeTree.kt @@ -0,0 +1,261 @@ +package monke.trees + +import monke.nodes.Entry +import monke.nodes.TwoThreeTreeNode +import monke.trees.treeInterfaces.BTree + +public class TwoThreeTree, V> : BTree{ + + protected var root: TwoThreeTreeNode? = null + var size = 0 + private set + + override fun search(key: K) : V?{ + return getRecursive(root, key) + } + + override fun insert(key: K, value: V): V?{ + + if(root == null){ + root = TwoThreeTreeNode(entries = mutableListOf(Entry(key, value))) + size = 1 + return value + } + + var node = root + + while(!node!!.isLeaf){ + node = chooseChild(node, key) + } + + insertEntryInNode(node, Entry(key, value)) + size++ + + var current = node + while(current != null && current.entries.size == 3){ + splitNode(current) + current = current.parent + } + return value + } + + override fun delete(key: K): V? { + val node = findNode(root, key) ?: return null + + if(node.isLeaf){ + val oldValue = removeEntry(node, key) + fixUnderFlow(node) + size-- + return oldValue + } + + val index = node.entries.indexOfFirst { it.key == key } + val leftChild = node.children[index] + var replacementNode = leftChild + + while(!replacementNode.isLeaf){ + replacementNode = replacementNode.children.last() + } + + val replacementEntry = replacementNode.entries.last() + + node.entries[index] = Entry(replacementEntry.key, replacementEntry.value) + + removeEntry(replacementNode, replacementEntry.key) + fixUnderFlow(replacementNode) + size-- + + return replacementEntry.value + } + + private fun getRecursive(node: TwoThreeTreeNode?, key: K) : V?{ + if(node == null) return null + + node.entries.find { it.key == key }?.let { return it.value } + + return if (node.isLeaf) null else getRecursive(chooseChild(node, key), key) + } + + private fun chooseChild(node: TwoThreeTreeNode, key: K) : TwoThreeTreeNode { + val listOfKey = node.entries + return when (listOfKey.size){ + 1 -> if(key < listOfKey[0].key) node.children[0] else node.children[1] + 2 -> when{ + key < listOfKey[0].key -> node.children[0] + key > listOfKey[1].key -> node.children[2] + else -> node.children[1] + } + else -> throw IllegalArgumentException("incorrect number of key in node") + } + } + + private fun insertEntryInNode(node: TwoThreeTreeNode, entry: Entry){ + val index = node.entries.indexOfFirst { it.key > entry.key } + if (index == -1) node.entries.add(entry) + else node.entries.add(index, entry) + } + + private fun splitNode(node: TwoThreeTreeNode) { + if (node.entries.size != 3) return + + val leftEntry = node.entries[0] + val middleEntry = node.entries[1] + val rightEntry = node.entries[2] + + val leftNode = TwoThreeTreeNode(entries = mutableListOf(leftEntry), parent = node.parent) + val rightNode = TwoThreeTreeNode(entries = mutableListOf(rightEntry), parent = node.parent) + + if (!node.isLeaf) { + val mid = node.children.size / 2 + leftNode.children.addAll(node.children.subList(0, mid)) + rightNode.children.addAll(node.children.subList(mid, node.children.size)) + leftNode.children.forEach { it.parent = leftNode } + rightNode.children.forEach { it.parent = rightNode } + } + + if (node.parent == null){ + val newRoot = TwoThreeTreeNode(entries = mutableListOf(middleEntry)) + newRoot.children.add(leftNode) + newRoot.children.add(rightNode) + leftNode.parent = newRoot + rightNode.parent = newRoot + root = newRoot + } else { + val parent = node.parent + val index = parent!!.entries.indexOfFirst { it.key > middleEntry.key } + if (index == -1) parent.entries.add(middleEntry) + else parent.entries.add(index, middleEntry) + + val childIndex = parent.children.indexOf(node) + parent.children.removeAt(childIndex) + parent.children.add(childIndex, leftNode) + parent.children.add(childIndex + 1, rightNode) + } + } + + private fun findNode(node : TwoThreeTreeNode?, key : K): TwoThreeTreeNode?{ + var currentNode = node + + while(currentNode != null){ + + currentNode.entries.find { it.key == key }?.let { return currentNode } + + if(currentNode.isLeaf) return null + + currentNode = chooseChild(currentNode, key) + } + return null + } + + private fun removeEntry(node : TwoThreeTreeNode, key : K): V?{ + val index = node.entries.indexOfFirst { it.key == key } + + if(index != -1){ + val entry = node.entries.removeAt(index) + return entry.value + } else return null + } + + private fun fixUnderFlow(node : TwoThreeTreeNode){ + if(node.entries.size >= 1) return + + val parent = node.parent ?: run { + if(node.entries.isEmpty() && node.children.isNotEmpty()){ + root = node.children.first() + root?.parent = null + } + return + } + + val index = parent.children.indexOf(node) + val leftSibling = if(index > 0) parent.children[index - 1] else null + val rightSibling = if(index < parent.children.size - 1) parent.children[index+1] else null + + if(leftSibling != null && leftSibling.entries.size > 1){ + borrowFromLeft(node, leftSibling, parent, index) + return + } + if(rightSibling != null && rightSibling.entries.size > 1){ + borrowFromRight(node, rightSibling, parent, index) + return + } + if(leftSibling != null){ + mergeWithLeft(node, leftSibling,parent, index) + } + else if(rightSibling != null){ + mergeWithRight(node, rightSibling, parent, index) + } + } + + private fun borrowFromLeft( + node: TwoThreeTreeNode, + leftSibling: TwoThreeTreeNode, + parent: TwoThreeTreeNode, + index: Int + ){ + val borrowEntry = leftSibling.entries.removeAt(leftSibling.entries.lastIndex) + val parentEntry = parent.entries[index - 1] + + node.entries.add(0, parentEntry) + parent.entries[index - 1] = borrowEntry + + if(leftSibling.children.isNotEmpty()){ + val child = leftSibling.children.removeAt(leftSibling.children.lastIndex) + node.children.add(0, child) + child.parent = node + } + } + + private fun borrowFromRight( + node: TwoThreeTreeNode, + rightSibling: TwoThreeTreeNode, + parent: TwoThreeTreeNode, + index: Int + ){ + val borrowEntry = rightSibling.entries.removeAt(0) + val parentEntry = parent.entries[index] + + node.entries.add(parentEntry) + parent.entries[index] = borrowEntry + + if(rightSibling.children.isNotEmpty()){ + val child = rightSibling.children.removeAt(0) + node.children.add(child) + child.parent = node + } + } + + private fun mergeWithLeft( + node: TwoThreeTreeNode, + leftSibling: TwoThreeTreeNode, + parent: TwoThreeTreeNode, + index: Int + ) { + leftSibling.entries.add(parent.entries[index - 1]) + leftSibling.entries.addAll(node.entries) + leftSibling.children.addAll(node.children) + node.children.forEach { it.parent = leftSibling } + + parent.entries.removeAt(index - 1) + parent.children.removeAt(index) + + fixUnderFlow(parent) + } + + private fun mergeWithRight( + node: TwoThreeTreeNode, + rightSibling: TwoThreeTreeNode, + parent: TwoThreeTreeNode, + index: Int + ){ + node.entries.add(parent.entries[index]) + node.entries.addAll(rightSibling.entries) + node.children.addAll(rightSibling.children) + rightSibling.children.forEach { it.parent = node } + + parent.entries.removeAt(index) + parent.children.removeAt(index + 1) + + fixUnderFlow(parent) + } +} \ No newline at end of file diff --git a/lib/src/main/kotlin/monke/trees/treeInterfaces/BTree.kt b/lib/src/main/kotlin/monke/trees/treeInterfaces/BTree.kt new file mode 100644 index 0000000..8417e4e --- /dev/null +++ b/lib/src/main/kotlin/monke/trees/treeInterfaces/BTree.kt @@ -0,0 +1,10 @@ +package monke.trees.treeInterfaces + +interface BTree, V> { + + fun search(key: K): V? + + fun insert(key: K, value: V): V? + + fun delete(key: K): V? +} diff --git a/lib/src/test/kotlin/monke/trees/TwoThreeTreeTest.kt b/lib/src/test/kotlin/monke/trees/TwoThreeTreeTest.kt new file mode 100644 index 0000000..8e632c8 --- /dev/null +++ b/lib/src/test/kotlin/monke/trees/TwoThreeTreeTest.kt @@ -0,0 +1,79 @@ +package monke.trees + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import java.util.* + +class TwoThreeTreeTest { + + @Test + fun `insert and search single element`() { + val tree = TwoThreeTree() + assertNull(tree.search(10)) + + tree.insert(10, "a") + assertEquals("a", tree.search(10)) + assertEquals(1, tree.size) + } + + @Test + fun `insert multiple elements and preserve order`() { + val tree = TwoThreeTree() + val values = (1..10).shuffled() + values.forEach { tree.insert(it, "val$it") } + + (1..10).forEach { + assertEquals("val$it", tree.search(it)) + } + assertEquals(10, tree.size) + } + + @Test + fun `delete leaf element`() { + val tree = TwoThreeTree() + tree.insert(1, "one") + tree.insert(2, "two") + tree.insert(3, "three") + + val removed = tree.delete(3) + assertEquals("three", removed) + assertNull(tree.search(3)) + assertEquals(2, tree.size) + } + + @Test + fun `delete internal node element`() { + val tree = TwoThreeTree() + (1..5).forEach { tree.insert(it, "val$it") } + + val removed = tree.delete(3) + assertEquals("val3", removed) + assertNull(tree.search(3)) + assertEquals(4, tree.size) + } + + @Test + fun `insert and delete sequence like TreeMap`() { + val tree = TwoThreeTree() + val ref = TreeMap() + + (1..50).shuffled().forEach { + tree.insert(it, it) + ref[it] = it + } + + // проверим, что все значения совпадают + ref.forEach { (k, v) -> + assertEquals(v, tree.search(k)) + } + + (1..50).shuffled().forEach { + val deleted = tree.delete(it) + val expected = ref.remove(it) + assertEquals(expected, deleted) + } + + assertEquals(0, tree.size) + assertTrue(ref.isEmpty()) + } +} From def5270127e3541fa9f09842fb1ff6e0354b1cc7 Mon Sep 17 00:00:00 2001 From: TheFollan Date: Mon, 29 Sep 2025 17:36:20 +0300 Subject: [PATCH 05/12] Added documentation --- .../kotlin/monke/nodes/TwoThreeTreeNode.kt | 2 +- .../main/kotlin/monke/trees/TwoThreeTree.kt | 47 +++++++++++++++++-- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/lib/src/main/kotlin/monke/nodes/TwoThreeTreeNode.kt b/lib/src/main/kotlin/monke/nodes/TwoThreeTreeNode.kt index 4d21426..170ebfb 100644 --- a/lib/src/main/kotlin/monke/nodes/TwoThreeTreeNode.kt +++ b/lib/src/main/kotlin/monke/nodes/TwoThreeTreeNode.kt @@ -7,7 +7,7 @@ data class Entry, V>( public class TwoThreeTreeNode, V> ( val entries: MutableList> = mutableListOf(), val children: MutableList> = mutableListOf(), - var paretn: TwoThreeTreeNode? = null + var parent: TwoThreeTreeNode? = null ) { val isLeaf : Boolean get() = children.isEmpty() diff --git a/lib/src/main/kotlin/monke/trees/TwoThreeTree.kt b/lib/src/main/kotlin/monke/trees/TwoThreeTree.kt index 0043556..de61123 100644 --- a/lib/src/main/kotlin/monke/trees/TwoThreeTree.kt +++ b/lib/src/main/kotlin/monke/trees/TwoThreeTree.kt @@ -3,17 +3,46 @@ package monke.trees import monke.nodes.Entry import monke.nodes.TwoThreeTreeNode import monke.trees.treeInterfaces.BTree - +/** + * Implementation of a 2-3 Tree data structure. + * + * A 2-3 Tree is a balanced search tree where every internal node + * can contain either one key (2-node) or two keys (3-node), and + * accordingly has 2 or 3 children. The tree guarantees logarithmic + * time complexity for search, insertion, and deletion operations. + * + * @param K the type of keys, must implement [Comparable] + * @param V the type of values stored in the tree + */ public class TwoThreeTree, V> : BTree{ - + /** + * Current root of the tree, or `null` if the tree is empty. + */ protected var root: TwoThreeTreeNode? = null + /** + * Number of key-value pairs currently stored in the tree. + */ var size = 0 private set - + /** + * Searches for a value by the given [key]. + * + * @param key the key to search for + * @return the value associated with the key, or `null` if not found + */ override fun search(key: K) : V?{ return getRecursive(root, key) } - + /** + * Inserts a new key-value pair into the tree. + * + * If the key already exists, its value will be replaced. + * Splitting is performed automatically when nodes overflow. + * + * @param key the key to insert + * @param value the value to associate with the key + * @return the value that was inserted + */ override fun insert(key: K, value: V): V?{ if(root == null){ @@ -38,7 +67,15 @@ public class TwoThreeTree, V> : BTree{ } return value } - + /** + * Deletes a key (and its associated value) from the tree. + * + * Balancing is performed automatically (borrowing or merging) + * if a node underflows. + * + * @param key the key to delete + * @return the value that was removed, or `null` if the key was not found + */ override fun delete(key: K): V? { val node = findNode(root, key) ?: return null From 47fcff5090e9833266f80e9a104dfd89f70d2264 Mon Sep 17 00:00:00 2001 From: TheFollan Date: Mon, 29 Sep 2025 17:37:55 +0300 Subject: [PATCH 06/12] Changed README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index df75cb9..4c35c68 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ * Binary Search Tree * AVL Tree * Red-Black Tree +* Two-Three Tree ## 🛠️ Quick Start From 72717dd3e590145b4846f8c4dbc5b377edb6f5d5 Mon Sep 17 00:00:00 2001 From: TheFollan Date: Mon, 29 Sep 2025 18:00:58 +0300 Subject: [PATCH 07/12] Applied ktlint --- .../kotlin/monke/nodes/TwoThreeTreeNode.kt | 10 +- .../main/kotlin/monke/trees/TwoThreeTree.kt | 154 +++++++++++------- .../monke/trees/treeInterfaces/BTree.kt | 6 +- .../kotlin/monke/trees/TwoThreeTreeTest.kt | 8 +- 4 files changed, 104 insertions(+), 74 deletions(-) diff --git a/lib/src/main/kotlin/monke/nodes/TwoThreeTreeNode.kt b/lib/src/main/kotlin/monke/nodes/TwoThreeTreeNode.kt index 170ebfb..e2224d4 100644 --- a/lib/src/main/kotlin/monke/nodes/TwoThreeTreeNode.kt +++ b/lib/src/main/kotlin/monke/nodes/TwoThreeTreeNode.kt @@ -2,14 +2,14 @@ package monke.nodes data class Entry, V>( val key: K, - var value: V + var value: V, ) -public class TwoThreeTreeNode, V> ( + +public class TwoThreeTreeNode, V>( val entries: MutableList> = mutableListOf(), val children: MutableList> = mutableListOf(), - var parent: TwoThreeTreeNode? = null + var parent: TwoThreeTreeNode? = null, ) { - val isLeaf : Boolean + val isLeaf: Boolean get() = children.isEmpty() } - diff --git a/lib/src/main/kotlin/monke/trees/TwoThreeTree.kt b/lib/src/main/kotlin/monke/trees/TwoThreeTree.kt index de61123..7e38740 100644 --- a/lib/src/main/kotlin/monke/trees/TwoThreeTree.kt +++ b/lib/src/main/kotlin/monke/trees/TwoThreeTree.kt @@ -3,6 +3,7 @@ package monke.trees import monke.nodes.Entry import monke.nodes.TwoThreeTreeNode import monke.trees.treeInterfaces.BTree + /** * Implementation of a 2-3 Tree data structure. * @@ -14,25 +15,26 @@ import monke.trees.treeInterfaces.BTree * @param K the type of keys, must implement [Comparable] * @param V the type of values stored in the tree */ -public class TwoThreeTree, V> : BTree{ +public class TwoThreeTree, V> : BTree { /** * Current root of the tree, or `null` if the tree is empty. */ protected var root: TwoThreeTreeNode? = null + /** * Number of key-value pairs currently stored in the tree. */ var size = 0 private set + /** * Searches for a value by the given [key]. * * @param key the key to search for * @return the value associated with the key, or `null` if not found */ - override fun search(key: K) : V?{ - return getRecursive(root, key) - } + override fun search(key: K): V? = getRecursive(root, key) + /** * Inserts a new key-value pair into the tree. * @@ -43,9 +45,11 @@ public class TwoThreeTree, V> : BTree{ * @param value the value to associate with the key * @return the value that was inserted */ - override fun insert(key: K, value: V): V?{ - - if(root == null){ + override fun insert( + key: K, + value: V, + ): V? { + if (root == null) { root = TwoThreeTreeNode(entries = mutableListOf(Entry(key, value))) size = 1 return value @@ -53,7 +57,7 @@ public class TwoThreeTree, V> : BTree{ var node = root - while(!node!!.isLeaf){ + while (!node!!.isLeaf) { node = chooseChild(node, key) } @@ -61,12 +65,13 @@ public class TwoThreeTree, V> : BTree{ size++ var current = node - while(current != null && current.entries.size == 3){ + while (current != null && current.entries.size == 3) { splitNode(current) current = current.parent } return value } + /** * Deletes a key (and its associated value) from the tree. * @@ -79,8 +84,8 @@ public class TwoThreeTree, V> : BTree{ override fun delete(key: K): V? { val node = findNode(root, key) ?: return null - if(node.isLeaf){ - val oldValue = removeEntry(node, key) + if (node.isLeaf) { + val oldValue = removeEntry(node, key) fixUnderFlow(node) size-- return oldValue @@ -90,7 +95,7 @@ public class TwoThreeTree, V> : BTree{ val leftChild = node.children[index] var replacementNode = leftChild - while(!replacementNode.isLeaf){ + while (!replacementNode.isLeaf) { replacementNode = replacementNode.children.last() } @@ -105,31 +110,44 @@ public class TwoThreeTree, V> : BTree{ return replacementEntry.value } - private fun getRecursive(node: TwoThreeTreeNode?, key: K) : V?{ - if(node == null) return null + private fun getRecursive( + node: TwoThreeTreeNode?, + key: K, + ): V? { + if (node == null) return null node.entries.find { it.key == key }?.let { return it.value } return if (node.isLeaf) null else getRecursive(chooseChild(node, key), key) } - private fun chooseChild(node: TwoThreeTreeNode, key: K) : TwoThreeTreeNode { + private fun chooseChild( + node: TwoThreeTreeNode, + key: K, + ): TwoThreeTreeNode { val listOfKey = node.entries - return when (listOfKey.size){ - 1 -> if(key < listOfKey[0].key) node.children[0] else node.children[1] - 2 -> when{ - key < listOfKey[0].key -> node.children[0] - key > listOfKey[1].key -> node.children[2] - else -> node.children[1] - } + return when (listOfKey.size) { + 1 -> if (key < listOfKey[0].key) node.children[0] else node.children[1] + 2 -> + when { + key < listOfKey[0].key -> node.children[0] + key > listOfKey[1].key -> node.children[2] + else -> node.children[1] + } else -> throw IllegalArgumentException("incorrect number of key in node") } } - private fun insertEntryInNode(node: TwoThreeTreeNode, entry: Entry){ + private fun insertEntryInNode( + node: TwoThreeTreeNode, + entry: Entry, + ) { val index = node.entries.indexOfFirst { it.key > entry.key } - if (index == -1) node.entries.add(entry) - else node.entries.add(index, entry) + if (index == -1) { + node.entries.add(entry) + } else { + node.entries.add(index, entry) + } } private fun splitNode(node: TwoThreeTreeNode) { @@ -150,7 +168,7 @@ public class TwoThreeTree, V> : BTree{ rightNode.children.forEach { it.parent = rightNode } } - if (node.parent == null){ + if (node.parent == null) { val newRoot = TwoThreeTreeNode(entries = mutableListOf(middleEntry)) newRoot.children.add(leftNode) newRoot.children.add(rightNode) @@ -160,8 +178,11 @@ public class TwoThreeTree, V> : BTree{ } else { val parent = node.parent val index = parent!!.entries.indexOfFirst { it.key > middleEntry.key } - if (index == -1) parent.entries.add(middleEntry) - else parent.entries.add(index, middleEntry) + if (index == -1) { + parent.entries.add(middleEntry) + } else { + parent.entries.add(index, middleEntry) + } val childIndex = parent.children.indexOf(node) parent.children.removeAt(childIndex) @@ -170,56 +191,63 @@ public class TwoThreeTree, V> : BTree{ } } - private fun findNode(node : TwoThreeTreeNode?, key : K): TwoThreeTreeNode?{ + private fun findNode( + node: TwoThreeTreeNode?, + key: K, + ): TwoThreeTreeNode? { var currentNode = node - while(currentNode != null){ - + while (currentNode != null) { currentNode.entries.find { it.key == key }?.let { return currentNode } - if(currentNode.isLeaf) return null + if (currentNode.isLeaf) return null currentNode = chooseChild(currentNode, key) } return null } - private fun removeEntry(node : TwoThreeTreeNode, key : K): V?{ + private fun removeEntry( + node: TwoThreeTreeNode, + key: K, + ): V? { val index = node.entries.indexOfFirst { it.key == key } - if(index != -1){ + if (index != -1) { val entry = node.entries.removeAt(index) return entry.value - } else return null + } else { + return null + } } - private fun fixUnderFlow(node : TwoThreeTreeNode){ - if(node.entries.size >= 1) return + private fun fixUnderFlow(node: TwoThreeTreeNode) { + if (node.entries.size >= 1) return - val parent = node.parent ?: run { - if(node.entries.isEmpty() && node.children.isNotEmpty()){ - root = node.children.first() - root?.parent = null + val parent = + node.parent ?: run { + if (node.entries.isEmpty() && node.children.isNotEmpty()) { + root = node.children.first() + root?.parent = null + } + return } - return - } val index = parent.children.indexOf(node) - val leftSibling = if(index > 0) parent.children[index - 1] else null - val rightSibling = if(index < parent.children.size - 1) parent.children[index+1] else null + val leftSibling = if (index > 0) parent.children[index - 1] else null + val rightSibling = if (index < parent.children.size - 1) parent.children[index + 1] else null - if(leftSibling != null && leftSibling.entries.size > 1){ + if (leftSibling != null && leftSibling.entries.size > 1) { borrowFromLeft(node, leftSibling, parent, index) return } - if(rightSibling != null && rightSibling.entries.size > 1){ + if (rightSibling != null && rightSibling.entries.size > 1) { borrowFromRight(node, rightSibling, parent, index) return } - if(leftSibling != null){ - mergeWithLeft(node, leftSibling,parent, index) - } - else if(rightSibling != null){ + if (leftSibling != null) { + mergeWithLeft(node, leftSibling, parent, index) + } else if (rightSibling != null) { mergeWithRight(node, rightSibling, parent, index) } } @@ -228,15 +256,15 @@ public class TwoThreeTree, V> : BTree{ node: TwoThreeTreeNode, leftSibling: TwoThreeTreeNode, parent: TwoThreeTreeNode, - index: Int - ){ + index: Int, + ) { val borrowEntry = leftSibling.entries.removeAt(leftSibling.entries.lastIndex) val parentEntry = parent.entries[index - 1] node.entries.add(0, parentEntry) parent.entries[index - 1] = borrowEntry - if(leftSibling.children.isNotEmpty()){ + if (leftSibling.children.isNotEmpty()) { val child = leftSibling.children.removeAt(leftSibling.children.lastIndex) node.children.add(0, child) child.parent = node @@ -245,17 +273,17 @@ public class TwoThreeTree, V> : BTree{ private fun borrowFromRight( node: TwoThreeTreeNode, - rightSibling: TwoThreeTreeNode, + rightSibling: TwoThreeTreeNode, parent: TwoThreeTreeNode, - index: Int - ){ + index: Int, + ) { val borrowEntry = rightSibling.entries.removeAt(0) val parentEntry = parent.entries[index] node.entries.add(parentEntry) parent.entries[index] = borrowEntry - if(rightSibling.children.isNotEmpty()){ + if (rightSibling.children.isNotEmpty()) { val child = rightSibling.children.removeAt(0) node.children.add(child) child.parent = node @@ -266,7 +294,7 @@ public class TwoThreeTree, V> : BTree{ node: TwoThreeTreeNode, leftSibling: TwoThreeTreeNode, parent: TwoThreeTreeNode, - index: Int + index: Int, ) { leftSibling.entries.add(parent.entries[index - 1]) leftSibling.entries.addAll(node.entries) @@ -281,10 +309,10 @@ public class TwoThreeTree, V> : BTree{ private fun mergeWithRight( node: TwoThreeTreeNode, - rightSibling: TwoThreeTreeNode, + rightSibling: TwoThreeTreeNode, parent: TwoThreeTreeNode, - index: Int - ){ + index: Int, + ) { node.entries.add(parent.entries[index]) node.entries.addAll(rightSibling.entries) node.children.addAll(rightSibling.children) @@ -295,4 +323,4 @@ public class TwoThreeTree, V> : BTree{ fixUnderFlow(parent) } -} \ No newline at end of file +} diff --git a/lib/src/main/kotlin/monke/trees/treeInterfaces/BTree.kt b/lib/src/main/kotlin/monke/trees/treeInterfaces/BTree.kt index 8417e4e..fdbc040 100644 --- a/lib/src/main/kotlin/monke/trees/treeInterfaces/BTree.kt +++ b/lib/src/main/kotlin/monke/trees/treeInterfaces/BTree.kt @@ -1,10 +1,12 @@ package monke.trees.treeInterfaces interface BTree, V> { - fun search(key: K): V? - fun insert(key: K, value: V): V? + fun insert( + key: K, + value: V, + ): V? fun delete(key: K): V? } diff --git a/lib/src/test/kotlin/monke/trees/TwoThreeTreeTest.kt b/lib/src/test/kotlin/monke/trees/TwoThreeTreeTest.kt index 8e632c8..5078727 100644 --- a/lib/src/test/kotlin/monke/trees/TwoThreeTreeTest.kt +++ b/lib/src/test/kotlin/monke/trees/TwoThreeTreeTest.kt @@ -1,11 +1,12 @@ package monke.trees -import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test -import java.util.* +import java.util.TreeMap +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue class TwoThreeTreeTest { - @Test fun `insert and search single element`() { val tree = TwoThreeTree() @@ -62,7 +63,6 @@ class TwoThreeTreeTest { ref[it] = it } - // проверим, что все значения совпадают ref.forEach { (k, v) -> assertEquals(v, tree.search(k)) } From 7d3873afea239bc02b4178c4040da215febdf23d Mon Sep 17 00:00:00 2001 From: TheFollan Date: Mon, 29 Sep 2025 18:12:09 +0300 Subject: [PATCH 08/12] minor fixes --- lib/src/main/kotlin/monke/trees/TwoThreeTree.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/src/main/kotlin/monke/trees/TwoThreeTree.kt b/lib/src/main/kotlin/monke/trees/TwoThreeTree.kt index 7e38740..c09298f 100644 --- a/lib/src/main/kotlin/monke/trees/TwoThreeTree.kt +++ b/lib/src/main/kotlin/monke/trees/TwoThreeTree.kt @@ -92,14 +92,14 @@ public class TwoThreeTree, V> : BTree { } val index = node.entries.indexOfFirst { it.key == key } - val leftChild = node.children[index] - var replacementNode = leftChild + val rightChild = node.children[index] + var replacementNode = rightChild while (!replacementNode.isLeaf) { - replacementNode = replacementNode.children.last() + replacementNode = replacementNode.children.first() } - val replacementEntry = replacementNode.entries.last() + val replacementEntry = replacementNode.entries.first() node.entries[index] = Entry(replacementEntry.key, replacementEntry.value) @@ -280,7 +280,7 @@ public class TwoThreeTree, V> : BTree { val borrowEntry = rightSibling.entries.removeAt(0) val parentEntry = parent.entries[index] - node.entries.add(parentEntry) + node.entries.add(0, parentEntry) parent.entries[index] = borrowEntry if (rightSibling.children.isNotEmpty()) { From 68121c18b49b2b21b8206b8dd738d9dcbef63fa0 Mon Sep 17 00:00:00 2001 From: TheFollan Date: Mon, 29 Sep 2025 18:27:49 +0300 Subject: [PATCH 09/12] minor fixes --- lib/src/main/kotlin/monke/trees/TwoThreeTree.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/src/main/kotlin/monke/trees/TwoThreeTree.kt b/lib/src/main/kotlin/monke/trees/TwoThreeTree.kt index c09298f..00c168a 100644 --- a/lib/src/main/kotlin/monke/trees/TwoThreeTree.kt +++ b/lib/src/main/kotlin/monke/trees/TwoThreeTree.kt @@ -126,6 +126,7 @@ public class TwoThreeTree, V> : BTree { key: K, ): TwoThreeTreeNode { val listOfKey = node.entries + require(node.children.size == listOfKey.size + 1) { "Invalid children count" } return when (listOfKey.size) { 1 -> if (key < listOfKey[0].key) node.children[0] else node.children[1] 2 -> @@ -298,6 +299,7 @@ public class TwoThreeTree, V> : BTree { ) { leftSibling.entries.add(parent.entries[index - 1]) leftSibling.entries.addAll(node.entries) + leftSibling.entries.sortBy { it.key } leftSibling.children.addAll(node.children) node.children.forEach { it.parent = leftSibling } @@ -313,8 +315,9 @@ public class TwoThreeTree, V> : BTree { parent: TwoThreeTreeNode, index: Int, ) { - node.entries.add(parent.entries[index]) + node.entries.add(0, parent.entries[index]) node.entries.addAll(rightSibling.entries) + node.entries.sortBy { it.key } node.children.addAll(rightSibling.children) rightSibling.children.forEach { it.parent = node } From 223d60552780377f83b3b05c4dd0b3d6c1c7e9a3 Mon Sep 17 00:00:00 2001 From: TheFollan Date: Mon, 29 Sep 2025 18:48:59 +0300 Subject: [PATCH 10/12] fixed insert --- .../main/kotlin/monke/trees/TwoThreeTree.kt | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/src/main/kotlin/monke/trees/TwoThreeTree.kt b/lib/src/main/kotlin/monke/trees/TwoThreeTree.kt index 00c168a..c1d7437 100644 --- a/lib/src/main/kotlin/monke/trees/TwoThreeTree.kt +++ b/lib/src/main/kotlin/monke/trees/TwoThreeTree.kt @@ -61,6 +61,12 @@ public class TwoThreeTree, V> : BTree { node = chooseChild(node, key) } + val exsistingIndex = node.entries.indexOfFirst { it.key == key } + if (exsistingIndex != -1) { + node.entries[exsistingIndex].value = value + return value + } + insertEntryInNode(node, Entry(key, value)) size++ @@ -92,14 +98,14 @@ public class TwoThreeTree, V> : BTree { } val index = node.entries.indexOfFirst { it.key == key } - val rightChild = node.children[index] - var replacementNode = rightChild + val leftChild = node.children[index] + var replacementNode = leftChild while (!replacementNode.isLeaf) { - replacementNode = replacementNode.children.first() + replacementNode = replacementNode.children.last() } - val replacementEntry = replacementNode.entries.first() + val replacementEntry = replacementNode.entries.last() node.entries[index] = Entry(replacementEntry.key, replacementEntry.value) @@ -132,8 +138,8 @@ public class TwoThreeTree, V> : BTree { 2 -> when { key < listOfKey[0].key -> node.children[0] - key > listOfKey[1].key -> node.children[2] - else -> node.children[1] + key < listOfKey[1].key -> node.children[1] + else -> node.children[2] } else -> throw IllegalArgumentException("incorrect number of key in node") } @@ -299,7 +305,6 @@ public class TwoThreeTree, V> : BTree { ) { leftSibling.entries.add(parent.entries[index - 1]) leftSibling.entries.addAll(node.entries) - leftSibling.entries.sortBy { it.key } leftSibling.children.addAll(node.children) node.children.forEach { it.parent = leftSibling } @@ -317,7 +322,6 @@ public class TwoThreeTree, V> : BTree { ) { node.entries.add(0, parent.entries[index]) node.entries.addAll(rightSibling.entries) - node.entries.sortBy { it.key } node.children.addAll(rightSibling.children) rightSibling.children.forEach { it.parent = node } From 71eeda92fd1375f2e5fc57aecdd76a38ebb50b03 Mon Sep 17 00:00:00 2001 From: TheFollan Date: Mon, 29 Sep 2025 18:53:56 +0300 Subject: [PATCH 11/12] minor fixes --- .../kotlin/monke/trees/TwoThreeTreeTest.kt | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/lib/src/test/kotlin/monke/trees/TwoThreeTreeTest.kt b/lib/src/test/kotlin/monke/trees/TwoThreeTreeTest.kt index 5078727..c3a4797 100644 --- a/lib/src/test/kotlin/monke/trees/TwoThreeTreeTest.kt +++ b/lib/src/test/kotlin/monke/trees/TwoThreeTreeTest.kt @@ -41,7 +41,6 @@ class TwoThreeTreeTest { assertNull(tree.search(3)) assertEquals(2, tree.size) } - @Test fun `delete internal node element`() { val tree = TwoThreeTree() @@ -53,27 +52,4 @@ class TwoThreeTreeTest { assertEquals(4, tree.size) } - @Test - fun `insert and delete sequence like TreeMap`() { - val tree = TwoThreeTree() - val ref = TreeMap() - - (1..50).shuffled().forEach { - tree.insert(it, it) - ref[it] = it - } - - ref.forEach { (k, v) -> - assertEquals(v, tree.search(k)) - } - - (1..50).shuffled().forEach { - val deleted = tree.delete(it) - val expected = ref.remove(it) - assertEquals(expected, deleted) - } - - assertEquals(0, tree.size) - assertTrue(ref.isEmpty()) - } } From 8b151546cd955f5698e81db63c673373fd0d7641 Mon Sep 17 00:00:00 2001 From: TheFollan Date: Mon, 29 Sep 2025 18:54:31 +0300 Subject: [PATCH 12/12] ktlint --- lib/src/test/kotlin/monke/trees/TwoThreeTreeTest.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/src/test/kotlin/monke/trees/TwoThreeTreeTest.kt b/lib/src/test/kotlin/monke/trees/TwoThreeTreeTest.kt index c3a4797..fdf9c0a 100644 --- a/lib/src/test/kotlin/monke/trees/TwoThreeTreeTest.kt +++ b/lib/src/test/kotlin/monke/trees/TwoThreeTreeTest.kt @@ -1,10 +1,8 @@ package monke.trees import org.junit.jupiter.api.Test -import java.util.TreeMap import kotlin.test.assertEquals import kotlin.test.assertNull -import kotlin.test.assertTrue class TwoThreeTreeTest { @Test @@ -41,6 +39,7 @@ class TwoThreeTreeTest { assertNull(tree.search(3)) assertEquals(2, tree.size) } + @Test fun `delete internal node element`() { val tree = TwoThreeTree() @@ -51,5 +50,4 @@ class TwoThreeTreeTest { assertNull(tree.search(3)) assertEquals(4, tree.size) } - }