diff --git a/treedatastructure/src/main/java/com/github/adriankuta/datastructure/tree/TreeNode.kt b/treedatastructure/src/main/java/com/github/adriankuta/datastructure/tree/TreeNode.kt index 6d1051f..4cfa165 100644 --- a/treedatastructure/src/main/java/com/github/adriankuta/datastructure/tree/TreeNode.kt +++ b/treedatastructure/src/main/java/com/github/adriankuta/datastructure/tree/TreeNode.kt @@ -1,45 +1,94 @@ package com.github.adriankuta.datastructure.tree -open class TreeNode(val value: T) { +open class TreeNode(val value: T) : Iterable>, ChildDeclarationInterface { - private var parent: TreeNode? = null - private val children = mutableListOf>() + private var _parent: TreeNode? = null + /** + * The converse notion of a child, an immediate ancestor. + */ + val parent: TreeNode? + get() = _parent + private val _children = mutableListOf>() + /** + * A group of nodes with the same parent. + */ + val children: List> + get() = _children + + /** + * Add new child to current node or root. + * + * @param child A node which will be directly connected to current node. + */ fun addChild(child: TreeNode) { - child.parent = this - children += child + child._parent = this + _children.add(child) } + override fun child(value: T, childDeclaration: ChildDeclaration?) { + val newChild = TreeNode(value) + if(childDeclaration != null) + newChild.childDeclaration() + _children.add(newChild) + } + + /** + * Removes a single instance of the specified node from this tree, if it is present. + * + * @return `true` if the node has been successfully removed; `false` if it was not present in the tree. + */ fun removeChild(child: TreeNode): Boolean { - if (children.isEmpty()) { - return false - } - - val nestedChildRemoved = children.map { - it.removeChild(child) - }.reduce { acc, b -> acc or b } - - return children.remove(child) or nestedChildRemoved + val removed = child._parent?._children?.remove(child) + child._parent = null + return removed ?: false } - fun getParent(): TreeNode? = parent - - fun getChildren(): List> = children - - fun size(): Int { - if (children.isEmpty()) + /** + * This function go through tree and counts children. Root element is not counted. + * @return All child and nested child count. + */ + fun nodeCount(): Int { + if (_children.isEmpty()) return 0 - return children.size + - children.sumBy { it.size() } - + return _children.size + + _children.sumBy { it.nodeCount() } } - fun depth(): Int { - - val childrenMaxDepth = children.map { it.depth() }.max() ?: 0 + /** + * @return The number of edges on the longest path between current node and a descendant leaf. + */ + fun height(): Int { + val childrenMaxDepth = _children.map { it.height() } + .max() + ?: -1 // -1 because this method counts nodes, and edges are always one less then nodes. return childrenMaxDepth + 1 } + /** + * Distance is the number of edges along the shortest path between two nodes. + * @return The distance between current node and the root. + */ + fun depth(): Int { + var _depth = 0 + var tempParent = parent + + while (tempParent != null) { + _depth++ + tempParent = tempParent.parent + } + return _depth + } + + /** + * Remove all children from root and every node in tree. + */ + fun clear() { + _parent = null + _children.forEach { it.clear() } + _children.clear() + } + override fun toString(): String { val stringBuilder = StringBuilder() print(stringBuilder, "", "") @@ -50,14 +99,36 @@ open class TreeNode(val value: T) { stringBuilder.append(prefix) stringBuilder.append(value) stringBuilder.append('\n') - val childIterator = children.iterator() + val childIterator = _children.iterator() while (childIterator.hasNext()) { val node = childIterator.next() - if(childIterator.hasNext()) { + if (childIterator.hasNext()) { node.print(stringBuilder, "$childrenPrefix├── ", "$childrenPrefix│ ") } else { node.print(stringBuilder, "$childrenPrefix└── ", "$childrenPrefix ") } } } + + /** + * Tree is iterated by using `Pre-order Traversal Algorithm" + * 1. Check if the current node is empty or null. + * 2. Display the data part of the root (or current node). + * 3. Traverse the left subtree by recursively calling the pre-order function. + * 4. Traverse the right subtree by recursively calling the pre-order function. + * ``` + * E.g. + * 1 + * / | \ + * / | \ + * 2 3 4 + * / \ / | \ + * 5 6 7 8 9 + * / / | \ + * 10 11 12 13 + * + * Output: 1 2 5 10 6 11 12 13 3 4 7 8 9 + * ``` + */ + override fun iterator(): Iterator> = TreeNodeIterator(this) } \ No newline at end of file diff --git a/treedatastructure/src/main/java/com/github/adriankuta/datastructure/tree/TreeNodeExt.kt b/treedatastructure/src/main/java/com/github/adriankuta/datastructure/tree/TreeNodeExt.kt new file mode 100644 index 0000000..2652ff9 --- /dev/null +++ b/treedatastructure/src/main/java/com/github/adriankuta/datastructure/tree/TreeNodeExt.kt @@ -0,0 +1,15 @@ +package com.github.adriankuta.datastructure.tree + +typealias ChildDeclaration = ChildDeclarationInterface.() -> Unit + +@JvmSynthetic +inline fun treeNode(value: T, childDeclaration: ChildDeclaration): TreeNode { + val treeNode = TreeNode(value) + treeNode.childDeclaration() + return treeNode +} + +interface ChildDeclarationInterface { + + fun child(value: T, childDeclaration: ChildDeclaration? = null) +} \ No newline at end of file diff --git a/treedatastructure/src/main/java/com/github/adriankuta/datastructure/tree/TreeNodeIterator.kt b/treedatastructure/src/main/java/com/github/adriankuta/datastructure/tree/TreeNodeIterator.kt new file mode 100644 index 0000000..115c1c2 --- /dev/null +++ b/treedatastructure/src/main/java/com/github/adriankuta/datastructure/tree/TreeNodeIterator.kt @@ -0,0 +1,22 @@ +package com.github.adriankuta.datastructure.tree + +import java.util.* + +class TreeNodeIterator(root: TreeNode) : Iterator> { + + private val stack = Stack>() + + init { + stack.push(root) + } + + override fun hasNext(): Boolean = !stack.empty() + + override fun next(): TreeNode { + val node = stack.pop() + node.children + .asReversed() + .forEach { stack.push(it) } + return node + } +} \ No newline at end of file diff --git a/treedatastructure/src/test/java/com/github/adriankuta/datastructure/tree/TreeNodeTest.kt b/treedatastructure/src/test/java/com/github/adriankuta/datastructure/tree/TreeNodeTest.kt new file mode 100644 index 0000000..802f51e --- /dev/null +++ b/treedatastructure/src/test/java/com/github/adriankuta/datastructure/tree/TreeNodeTest.kt @@ -0,0 +1,131 @@ +package com.github.adriankuta.datastructure.tree + +import org.hamcrest.CoreMatchers.`is` +import org.hamcrest.CoreMatchers.nullValue +import org.junit.Assert.assertEquals +import org.junit.Assert.assertThat +import org.junit.Test + +class TreeNodeTest { + + @Test + fun removeNodeTest() { + val root = TreeNode("Root") + val beveragesNode = TreeNode("Beverages") + val curdNode = TreeNode("Curd") + root.addChild(beveragesNode) + root.addChild(curdNode) + + val teaNode = TreeNode("tea") + val coffeeNode = TreeNode("coffee") + val milkShakeNode = TreeNode("Milk Shake") + beveragesNode.addChild(teaNode) + beveragesNode.addChild(coffeeNode) + beveragesNode.addChild(milkShakeNode) + + val gingerTeaNode = TreeNode("ginger tea") + val normalTeaNode = TreeNode("normal tea") + teaNode.addChild(gingerTeaNode) + teaNode.addChild(normalTeaNode) + + val yogurtNode = TreeNode("yogurt") + val lassiNode = TreeNode("lassi") + curdNode.addChild(yogurtNode) + curdNode.addChild(lassiNode) + + assertEquals( + "Root\n" + + "├── Beverages\n" + + "│ ├── tea\n" + + "│ │ ├── ginger tea\n" + + "│ │ └── normal tea\n" + + "│ ├── coffee\n" + + "│ └── Milk Shake\n" + + "└── Curd\n" + + " ├── yogurt\n" + + " └── lassi\n", root.toString() + ) + + System.out.println("Remove: ${curdNode.value}") + root.removeChild(curdNode) + System.out.println("Remove: ${gingerTeaNode.value}") + root.removeChild(gingerTeaNode) + assertEquals( + "Root\n" + + "└── Beverages\n" + + " ├── tea\n" + + " │ └── normal tea\n" + + " ├── coffee\n" + + " └── Milk Shake\n", root.toString() + ) + } + + @Test + fun clearTest() { + val root = TreeNode("Root") + val beveragesNode = TreeNode("Beverages") + val curdNode = TreeNode("Curd") + root.addChild(beveragesNode) + root.addChild(curdNode) + + val teaNode = TreeNode("tea") + val coffeeNode = TreeNode("coffee") + val milkShakeNode = TreeNode("Milk Shake") + beveragesNode.addChild(teaNode) + beveragesNode.addChild(coffeeNode) + beveragesNode.addChild(milkShakeNode) + + val gingerTeaNode = TreeNode("ginger tea") + val normalTeaNode = TreeNode("normal tea") + teaNode.addChild(gingerTeaNode) + teaNode.addChild(normalTeaNode) + + val yogurtNode = TreeNode("yogurt") + val lassiNode = TreeNode("lassi") + curdNode.addChild(yogurtNode) + curdNode.addChild(lassiNode) + + println(root.toString()) + println(curdNode.height()) + + root.clear() + assertThat(root.children, `is`(emptyList())) + assertThat(beveragesNode.children, `is`(emptyList())) + assertThat(curdNode.children, `is`(emptyList())) + assertThat(teaNode.children, `is`(emptyList())) + assertThat(coffeeNode.children, `is`(emptyList())) + assertThat(milkShakeNode.children, `is`(emptyList())) + assertThat(gingerTeaNode.children, `is`(emptyList())) + assertThat(normalTeaNode.children, `is`(emptyList())) + assertThat(yogurtNode.children, `is`(emptyList())) + assertThat(lassiNode.children, `is`(emptyList())) + + assertThat(root.parent, `is`(nullValue())) + assertThat(beveragesNode.parent, `is`(nullValue())) + assertThat(curdNode.parent, `is`(nullValue())) + assertThat(teaNode.parent, `is`(nullValue())) + assertThat(coffeeNode.parent, `is`(nullValue())) + assertThat(milkShakeNode.parent, `is`(nullValue())) + assertThat(gingerTeaNode.parent, `is`(nullValue())) + assertThat(normalTeaNode.parent, `is`(nullValue())) + assertThat(yogurtNode.parent, `is`(nullValue())) + assertThat(lassiNode.parent, `is`(nullValue())) + } + + @Test + fun kotlinExtTest() { + val root = treeNode("World") { + child("North America") { + child("USA") + } + child("Europe") { + child("Poland") + child("Germany") + } + } + + println(root) + } + + +} \ No newline at end of file