mirror of
https://github.com/AdrianKuta/Tree-Data-Structure.git
synced 2026-06-19 19:00:14 +02:00
This commit is contained in:
@@ -6,6 +6,10 @@ All notable changes to this project are documented here. The format is based on
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- Structural mutation helpers on `TreeNode`: `insertChild`, `removeChildAt`, `replaceChild`,
|
||||
`moveChild`, `addChildren`, and `sortChildren`.
|
||||
|
||||
### Changed
|
||||
- Rewrote the README for clarity: one consistent example tree, task-oriented sections
|
||||
(building, traversal, navigation, functional, utilities, mutating), per-module usage, and a
|
||||
|
||||
@@ -10,6 +10,7 @@ public class com/github/adriankuta/datastructure/tree/TreeNode : com/github/adri
|
||||
public fun <init> (Ljava/lang/Object;Lcom/github/adriankuta/datastructure/tree/iterators/TreeNodeIterators;)V
|
||||
public synthetic fun <init> (Ljava/lang/Object;Lcom/github/adriankuta/datastructure/tree/iterators/TreeNodeIterators;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public final fun addChild (Lcom/github/adriankuta/datastructure/tree/TreeNode;)V
|
||||
public final fun addChildren ([Lcom/github/adriankuta/datastructure/tree/TreeNode;)V
|
||||
public synthetic fun child (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Lcom/github/adriankuta/datastructure/tree/TreeNode;
|
||||
public final fun clear ()V
|
||||
public final fun depth ()I
|
||||
@@ -19,13 +20,18 @@ public class com/github/adriankuta/datastructure/tree/TreeNode : com/github/adri
|
||||
public final fun getTreeIterator ()Lcom/github/adriankuta/datastructure/tree/iterators/TreeNodeIterators;
|
||||
public final fun getValue ()Ljava/lang/Object;
|
||||
public final fun height ()I
|
||||
public final fun insertChild (ILcom/github/adriankuta/datastructure/tree/TreeNode;)V
|
||||
public final fun isRoot ()Z
|
||||
public fun iterator ()Ljava/util/Iterator;
|
||||
public final fun iterator (Lcom/github/adriankuta/datastructure/tree/iterators/TreeNodeIterators;)Ljava/util/Iterator;
|
||||
public final fun moveChild (Lcom/github/adriankuta/datastructure/tree/TreeNode;I)Z
|
||||
public final fun nodeCount ()I
|
||||
public final fun path (Lcom/github/adriankuta/datastructure/tree/TreeNode;)Ljava/util/List;
|
||||
public final fun prettyString ()Ljava/lang/String;
|
||||
public final fun removeChild (Lcom/github/adriankuta/datastructure/tree/TreeNode;)Z
|
||||
public final fun removeChildAt (I)Lcom/github/adriankuta/datastructure/tree/TreeNode;
|
||||
public final fun replaceChild (ILcom/github/adriankuta/datastructure/tree/TreeNode;)Lcom/github/adriankuta/datastructure/tree/TreeNode;
|
||||
public final fun sortChildren (Ljava/util/Comparator;)V
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,19 @@ public open class TreeNode<T>(public val value: T, public val treeIterator: Tree
|
||||
* a cycle (i.e. [child] is this node or one of its ancestors).
|
||||
*/
|
||||
public fun addChild(child: TreeNode<T>) {
|
||||
validateAttachable(child)
|
||||
child._parent = this
|
||||
_children.add(child)
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that [child] can be attached as a direct child of this node, throwing if it cannot.
|
||||
*
|
||||
* @param child the node about to be attached.
|
||||
* @throws TreeNodeException if [child] already has a parent, or if attaching it here would create
|
||||
* a cycle (i.e. [child] is this node or one of its ancestors).
|
||||
*/
|
||||
private fun validateAttachable(child: TreeNode<T>) {
|
||||
if (child._parent != null) {
|
||||
throw TreeNodeException("$child already has a parent; call detach() before re-attaching it.")
|
||||
}
|
||||
@@ -78,8 +91,6 @@ public open class TreeNode<T>(public val value: T, public val treeIterator: Tree
|
||||
ancestor = ancestor._parent
|
||||
}
|
||||
}
|
||||
child._parent = this
|
||||
_children.add(child)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,6 +129,102 @@ public open class TreeNode<T>(public val value: T, public val treeIterator: Tree
|
||||
return removed
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts [child] as a direct child of this node at the given [index], shifting any existing
|
||||
* children at and after [index] one position to the right.
|
||||
*
|
||||
* @param index the position at which to insert [child]; must be in `0..children.size`.
|
||||
* @param child a node that is not already attached to a tree. To move a node that already has a
|
||||
* parent, call [detach] on it first.
|
||||
* @throws IndexOutOfBoundsException if [index] is out of range.
|
||||
* @throws TreeNodeException if [child] already has a parent, or if attaching it here would create
|
||||
* a cycle (i.e. [child] is this node or one of its ancestors).
|
||||
*/
|
||||
public fun insertChild(index: Int, child: TreeNode<T>) {
|
||||
validateAttachable(child)
|
||||
child._parent = this
|
||||
_children.add(index, child)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the direct child at the given [index], detaching it (its parent becomes `null`).
|
||||
*
|
||||
* @param index the position of the child to remove; must be in `0 until children.size`.
|
||||
* @return the detached child that was at [index].
|
||||
* @throws IndexOutOfBoundsException if [index] is out of range.
|
||||
*/
|
||||
public fun removeChildAt(index: Int): TreeNode<T> {
|
||||
val removed = _children.removeAt(index)
|
||||
removed._parent = null
|
||||
return removed
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the direct child at the given [index] with [child], detaching the previous child
|
||||
* (its parent becomes `null`).
|
||||
*
|
||||
* @param index the position of the child to replace; must be in `0 until children.size`.
|
||||
* @param child a node that is not already attached to a tree. To move a node that already has a
|
||||
* parent, call [detach] on it first.
|
||||
* @return the previous child that was at [index], now detached.
|
||||
* @throws IndexOutOfBoundsException if [index] is out of range.
|
||||
* @throws TreeNodeException if [child] already has a parent, or if attaching it here would create
|
||||
* a cycle (i.e. [child] is this node or one of its ancestors).
|
||||
*/
|
||||
public fun replaceChild(index: Int, child: TreeNode<T>): TreeNode<T> {
|
||||
validateAttachable(child)
|
||||
val old = _children[index]
|
||||
old._parent = null
|
||||
child._parent = this
|
||||
_children[index] = child
|
||||
return old
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves an existing direct [child] to a new position within this node's [children].
|
||||
*
|
||||
* [toIndex] is coerced into the valid range, so out-of-range targets clamp to the first or last
|
||||
* position. Because [child] is already a direct child, no re-parenting or cycle check is needed.
|
||||
*
|
||||
* @param child the node to reorder; must already be a direct child of this node.
|
||||
* @param toIndex the target position for [child] after removal, coerced into `0..children.size`.
|
||||
* @return `true` if [child] was a direct child and has been moved; `false` otherwise.
|
||||
*/
|
||||
public fun moveChild(child: TreeNode<T>, toIndex: Int): Boolean {
|
||||
val from = _children.indexOf(child)
|
||||
if (from < 0) return false
|
||||
_children.removeAt(from)
|
||||
_children.add(toIndex.coerceIn(0, _children.size), child)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds each of [children] as a direct child of this node, in order, validating each one the same
|
||||
* way as [addChild].
|
||||
*
|
||||
* Validation is performed per node as it is added, so if one node fails the children added before
|
||||
* it remain attached (the same partial-application behaviour as calling [addChild] in a loop).
|
||||
*
|
||||
* @param children nodes that are not already attached to a tree.
|
||||
* @throws TreeNodeException if any node already has a parent, or if attaching it here would create
|
||||
* a cycle (i.e. it is this node or one of its ancestors).
|
||||
*/
|
||||
public fun addChildren(vararg children: TreeNode<T>) {
|
||||
for (child in children) {
|
||||
addChild(child)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts this node's direct [children] in place according to the given [comparator]. Only the
|
||||
* immediate children are reordered; their subtrees are left untouched.
|
||||
*
|
||||
* @param comparator the comparator used to order the children.
|
||||
*/
|
||||
public fun sortChildren(comparator: Comparator<TreeNode<T>>) {
|
||||
_children.sortWith(comparator)
|
||||
}
|
||||
|
||||
/**
|
||||
* This function go through tree and counts children. Root element is not counted.
|
||||
* @return All child and nested child count.
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
package com.github.adriankuta.datastructure.tree
|
||||
|
||||
import com.github.adriankuta.datastructure.tree.exceptions.TreeNodeException
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertContentEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertNull
|
||||
import kotlin.test.assertSame
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class TreeNodeMutationTest {
|
||||
|
||||
@Test
|
||||
fun insertChildAtStartMiddleAndEnd() {
|
||||
val root = TreeNode("root")
|
||||
val a = TreeNode("a")
|
||||
val b = TreeNode("b")
|
||||
val c = TreeNode("c")
|
||||
val d = TreeNode("d")
|
||||
|
||||
root.insertChild(0, b) // [b]
|
||||
root.insertChild(0, a) // [a, b]
|
||||
root.insertChild(2, d) // [a, b, d] (end)
|
||||
root.insertChild(2, c) // [a, b, c, d] (middle)
|
||||
|
||||
assertContentEquals(listOf(a, b, c, d), root.children)
|
||||
// Each inserted node is re-parented to root.
|
||||
assertSame(root, a.parent)
|
||||
assertSame(root, b.parent)
|
||||
assertSame(root, c.parent)
|
||||
assertSame(root, d.parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeChildAtReturnsDetachedNodeAndClearsParent() {
|
||||
val root = TreeNode("root")
|
||||
val a = TreeNode("a")
|
||||
val b = TreeNode("b")
|
||||
val c = TreeNode("c")
|
||||
root.addChildren(a, b, c)
|
||||
|
||||
val removed = root.removeChildAt(1)
|
||||
|
||||
assertSame(b, removed)
|
||||
assertNull(removed.parent)
|
||||
assertContentEquals(listOf(a, c), root.children)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun replaceChildSwapsAndDetachesTheOld() {
|
||||
val root = TreeNode("root")
|
||||
val a = TreeNode("a")
|
||||
val b = TreeNode("b")
|
||||
val replacement = TreeNode("replacement")
|
||||
root.addChildren(a, b)
|
||||
|
||||
val old = root.replaceChild(0, replacement)
|
||||
|
||||
assertSame(a, old)
|
||||
assertNull(old.parent)
|
||||
assertSame(root, replacement.parent)
|
||||
assertContentEquals(listOf(replacement, b), root.children)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveChildReordersChildren() {
|
||||
val root = TreeNode("root")
|
||||
val a = TreeNode("a")
|
||||
val b = TreeNode("b")
|
||||
val c = TreeNode("c")
|
||||
root.addChildren(a, b, c)
|
||||
|
||||
assertTrue(root.moveChild(a, 2))
|
||||
assertContentEquals(listOf(b, c, a), root.children)
|
||||
// Parent pointer is unchanged after a move.
|
||||
assertSame(root, a.parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveChildReturnsFalseForNonChild() {
|
||||
val root = TreeNode("root")
|
||||
val a = TreeNode("a")
|
||||
root.addChild(a)
|
||||
|
||||
val stranger = TreeNode("stranger")
|
||||
assertFalse(root.moveChild(stranger, 0))
|
||||
assertContentEquals(listOf(a), root.children)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addChildrenAppendsAllInOrder() {
|
||||
val root = TreeNode("root")
|
||||
val a = TreeNode("a")
|
||||
val b = TreeNode("b")
|
||||
val c = TreeNode("c")
|
||||
|
||||
root.addChildren(a, b, c)
|
||||
|
||||
assertContentEquals(listOf(a, b, c), root.children)
|
||||
assertSame(root, a.parent)
|
||||
assertSame(root, b.parent)
|
||||
assertSame(root, c.parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addChildrenRejectsNodeThatAlreadyHasAParent() {
|
||||
val root = TreeNode("root")
|
||||
val attached = TreeNode("attached")
|
||||
TreeNode("other").addChild(attached)
|
||||
|
||||
assertFailsWith<TreeNodeException> { root.addChildren(attached) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun insertChildRejectsNodeThatAlreadyHasAParent() {
|
||||
val root = TreeNode("root")
|
||||
val attached = TreeNode("attached")
|
||||
TreeNode("other").addChild(attached)
|
||||
|
||||
assertFailsWith<TreeNodeException> { root.insertChild(0, attached) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun replaceChildRejectsNodeThatAlreadyHasAParent() {
|
||||
val root = TreeNode("root")
|
||||
val existing = TreeNode("existing")
|
||||
root.addChild(existing)
|
||||
|
||||
val attached = TreeNode("attached")
|
||||
TreeNode("other").addChild(attached)
|
||||
|
||||
assertFailsWith<TreeNodeException> { root.replaceChild(0, attached) }
|
||||
// The original child is untouched after a failed replace.
|
||||
assertContentEquals(listOf(existing), root.children)
|
||||
assertSame(root, existing.parent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sortChildrenReordersByComparator() {
|
||||
val root = TreeNode("root")
|
||||
val c = TreeNode("c")
|
||||
val a = TreeNode("a")
|
||||
val b = TreeNode("b")
|
||||
root.addChildren(c, a, b)
|
||||
|
||||
root.sortChildren(compareBy { it.value })
|
||||
|
||||
assertContentEquals(listOf(a, b, c), root.children)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user