diff --git a/csharp/Platform.Collections.Methods.Tests/TreesTests.cs b/csharp/Platform.Collections.Methods.Tests/TreesTests.cs index 8130eab..271021b 100644 --- a/csharp/Platform.Collections.Methods.Tests/TreesTests.cs +++ b/csharp/Platform.Collections.Methods.Tests/TreesTests.cs @@ -47,5 +47,38 @@ public static void SizedAndThreadedAVLBalancedTreeMultipleRandomAttachAndDetachT var avlTree = new SizedAndThreadedAVLBalancedTree(10000); avlTree.TestMultipleRandomCreationsAndDeletions(ref avlTree.Root, () => avlTree.Count, _n); } + + [Fact] + public static void SizedAndThreadedAVLBalancedTreeValidationTest() + { + var avlTree = new SizedAndThreadedAVLBalancedTree(100); + + // Test basic attach operations with validation + for (uint i = 1; i <= 10; i++) + { + var node = avlTree.Allocate(); + avlTree.Attach(ref avlTree.Root, node); + + // Validate tree structure after each insertion + // The validation will run automatically due to ENABLE_TREE_AUTO_DEBUG_AND_VALIDATION + } + + // Test detach operations with validation + for (uint i = 1; i <= 5; i++) + { + avlTree.Detach(ref avlTree.Root, i); + + // Validation runs automatically after detach + } + + // Test remaining elements + for (uint i = 6; i <= 10; i++) + { + avlTree.Detach(ref avlTree.Root, i); + } + + // Tree should be empty + Assert.Equal(0U, avlTree.Count); + } } } diff --git a/csharp/Platform.Collections.Methods/Trees/SizedAndThreadedAVLBalancedTreeMethods.cs b/csharp/Platform.Collections.Methods/Trees/SizedAndThreadedAVLBalancedTreeMethods.cs index a82a0bd..a8d09a7 100644 --- a/csharp/Platform.Collections.Methods/Trees/SizedAndThreadedAVLBalancedTreeMethods.cs +++ b/csharp/Platform.Collections.Methods/Trees/SizedAndThreadedAVLBalancedTreeMethods.cs @@ -427,6 +427,12 @@ protected override void AttachCore(ref TElement root, TElement node) } #if USEARRAYPOOL ArrayPool.Free(path); +#endif +#if ENABLE_TREE_AUTO_DEBUG_AND_VALIDATION + ValidateTree(root); + Debug.WriteLine("--AfterAttach--"); + Debug.WriteLine(PrintNodes(root)); + Debug.WriteLine("----------------"); #endif } } @@ -870,6 +876,12 @@ protected override void DetachCore(ref TElement root, TElement node) ClearNode(node); #if USEARRAYPOOL ArrayPool.Free(path); +#endif +#if ENABLE_TREE_AUTO_DEBUG_AND_VALIDATION + ValidateTree(root); + Debug.WriteLine("--AfterDetach--"); + Debug.WriteLine(PrintNodes(root)); + Debug.WriteLine("----------------"); #endif } } @@ -894,5 +906,112 @@ protected override void ClearNode(TElement node) SetRightIsChild(node, false); SetBalance(node, 0); } + +#if ENABLE_TREE_AUTO_DEBUG_AND_VALIDATION + /// + /// + /// Calculates the height of a tree node, following GLib's g_tree_node_height logic. + /// + /// + /// + /// + /// The node. + /// + /// + /// + /// The height of the node. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected int GetNodeHeight(TElement node) + { + if (node == TElement.Zero) + return 0; + + int leftHeight = 0; + int rightHeight = 0; + + if (GetLeftIsChild(node)) + leftHeight = GetNodeHeight(GetLeft(node)); + + if (GetRightIsChild(node)) + rightHeight = GetNodeHeight(GetRight(node)); + + return Math.Max(leftHeight, rightHeight) + 1; + } + + /// + /// + /// Validates the AVL tree node structure, following GLib's g_tree_node_check logic. + /// + /// + /// + /// + /// The node. + /// + /// + /// + /// Thrown when tree structure validation fails. + /// + /// + public void ValidateNodeStructure(TElement node) + { + if (node == TElement.Zero) + return; + + // Validate balance factor + int leftHeight = 0; + int rightHeight = 0; + + if (GetLeftIsChild(node)) + leftHeight = GetNodeHeight(GetLeft(node)); + + if (GetRightIsChild(node)) + rightHeight = GetNodeHeight(GetRight(node)); + + int calculatedBalance = rightHeight - leftHeight; + int storedBalance = GetBalance(node); + + if (calculatedBalance != storedBalance) + { + throw new InvalidOperationException($"Balance factor mismatch for node {node}. Expected: {calculatedBalance}, Actual: {storedBalance}"); + } + + // AVL property: balance factor must be -1, 0, or 1 + if (Math.Abs(calculatedBalance) > 1) + { + throw new InvalidOperationException($"AVL balance violation for node {node}. Balance factor: {calculatedBalance}"); + } + + // Recursively validate left and right subtrees + if (GetLeftIsChild(node)) + ValidateNodeStructure(GetLeft(node)); + + if (GetRightIsChild(node)) + ValidateNodeStructure(GetRight(node)); + } + + /// + /// + /// Validates the complete tree structure including sizes and AVL properties. + /// + /// + /// + /// + /// The root node. + /// + /// + public void ValidateTree(TElement root) + { + if (root == TElement.Zero) + return; + + // Validate sizes (from base class) + ValidateSizes(root); + + // Validate AVL structure + ValidateNodeStructure(root); + } +#endif } } diff --git a/csharp/Platform.Collections.Methods/Trees/SizedBinaryTreeMethodsBase.cs b/csharp/Platform.Collections.Methods/Trees/SizedBinaryTreeMethodsBase.cs index bdf5e30..ffe4d84 100644 --- a/csharp/Platform.Collections.Methods/Trees/SizedBinaryTreeMethodsBase.cs +++ b/csharp/Platform.Collections.Methods/Trees/SizedBinaryTreeMethodsBase.cs @@ -658,7 +658,7 @@ public void Detach(ref TElement root, TElement node) Debug.WriteLine("----------------"); ValidateSizes(root); var sizeAfter = GetSize(root); - if (!(Arithmetic.sizeBefore - TElement.One != sizeAfter)) + if (sizeBefore - TElement.One != sizeAfter) { throw new InvalidOperationException("Tree was broken after detach."); }