diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml deleted file mode 100644 index 50fbc4d..0000000 --- a/.github/workflows/CI.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: CI -env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -on: - push: - branches: [main] - pull_request: - branches: [main] - workflow_dispatch: -jobs: - build: - strategy: - matrix: - os: [macos-latest, ubuntu-latest, windows-latest] - smalltalk: [Pharo64-13, Pharo64-12, Pharo64-11, Pharo64-10] - runs-on: ${{ matrix.os }} - name: ${{ matrix.smalltalk }} on ${{ matrix.os }} - steps: - - uses: actions/checkout@v3 - - name: Setup SmalltalkCI - uses: hpi-swa/setup-smalltalkCI@v1 - with: - smalltalk-version: ${{ matrix.smalltalk }} - - name: Load and Test - run: smalltalkci -s ${{ matrix.smalltalk }} - shell: bash - timeout-minutes: 15 \ No newline at end of file diff --git a/.github/workflows/runTests.yml b/.github/workflows/runTests.yml new file mode 100644 index 0000000..eaf157a --- /dev/null +++ b/.github/workflows/runTests.yml @@ -0,0 +1,43 @@ +name: CI + +# Controls when the action will run. +on: + # Triggers the workflow on push or pull request events but only for the master branch + push: + branches: [ main ] + pull_request: + branches: [ main ] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + build: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.12.0 + with: + access_token: ${{ github.token }} + + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + # depth 0 will download all the repository history + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + # Runs tests in a pharo image by removing the old code in the pharo image + - name: Run Pharo Tests + id: tests + uses: akevalion/PharoTestsAction@v1 + with: + pharo: 'pharo12' + baseline: 'ContainersAVLTree' + group: 'default' + tests: 'AVL-Tree' diff --git a/.smalltalk.ston b/.smalltalk.ston deleted file mode 100644 index c388f16..0000000 --- a/.smalltalk.ston +++ /dev/null @@ -1,14 +0,0 @@ -SmalltalkCISpec { - #loading : [ - SCIMetacelloLoadSpec { - #baseline : 'ContainersAVLTree', - #directory : 'src', - #platforms : [ #pharo ] - } - ], - #testing : { - #coverage : { - #packages : [ 'Containers-AVL-Tree' ] - } - } -} \ No newline at end of file diff --git a/src/BaselineOfContainersAVLTree/BaselineOfContainersAVLTree.class.st b/src/BaselineOfContainersAVLTree/BaselineOfContainersAVLTree.class.st index d37a9e8..bad41bd 100644 --- a/src/BaselineOfContainersAVLTree/BaselineOfContainersAVLTree.class.st +++ b/src/BaselineOfContainersAVLTree/BaselineOfContainersAVLTree.class.st @@ -1,5 +1,5 @@ " -I represent Baseline of AVL Tree +I am a baseline " Class { #name : 'BaselineOfContainersAVLTree', @@ -10,10 +10,22 @@ Class { { #category : 'baselines' } BaselineOfContainersAVLTree >> baseline: spec [ - - spec for: #common do: [ - spec package: 'Containers-AVL-Tree'. - spec - package: 'Containers-AVL-Tree-Tests' - with: [ spec requires: #( 'Containers-AVL-Tree' ) ] ] -] \ No newline at end of file + + + spec for: #common do: [ "Packages" + spec package: 'Containers-AVL-Tree'. + spec + package: 'Containers-AVL-Tree-Tests' + with: [ spec requires: #( 'Containers-AVL-Tree' ) ]. + spec + package: 'Containers-AVL-Tree-Inspector' + with: [ spec requires: #( 'Containers-AVL-Tree' ) ]. + + "Define groups" + spec + group: 'Core' + with: #( 'Containers-AVL-Tree' 'Containers-AVL-Tree-Inspector' ); + group: 'Tests' with: #( 'Containers-AVL-Tree-Tests' ). + + spec group: 'default' with: #( 'Core' 'Tests' ) ] +] diff --git a/src/Containers-AVL-Tree-Tests/CTAVLTreeTest.class.st b/src/Containers-AVL-Tree-Tests/CTAVLTreeTest.class.st index 442fe52..18e27d1 100644 --- a/src/Containers-AVL-Tree-Tests/CTAVLTreeTest.class.st +++ b/src/Containers-AVL-Tree-Tests/CTAVLTreeTest.class.st @@ -1,9 +1,5 @@ " -I test the AVL Tree implementation (CTAVLTree). -I verify correctness of all tree operations including insertion, deletion, search, traversals, and collection protocol methods. I test edge cases like empty trees, single nodes, and complex removal scenarios. -I ensure the AVL tree maintains both BST ordering property and AVL balance property through all operations, including automatic rebalancing via rotations. -I test all four rotation cases and verify O(log n) height guarantees. - +An AVLTest is a test class for testing the behavior of AVL " Class { #name : 'CTAVLTreeTest', @@ -17,500 +13,340 @@ Class { { #category : 'running' } CTAVLTreeTest >> setUp [ - + super setUp. tree := CTAVLTree new ] { #category : 'tests' } -CTAVLTreeTest >> testAddMultipleElements [ - - tree addAll: #(50 30 70). - self assert: tree size equals: 3 -] - -{ #category : 'tests' } -CTAVLTreeTest >> testAddSingleElement [ - - tree add: 42. - self deny: tree isEmpty. - self assert: tree size equals: 1. - self assert: tree height equals: 1 -] - -{ #category : 'tests' } -CTAVLTreeTest >> testAnySatisfy [ - - tree addAll: #(50 30 70 20 40). - - self assert: (tree anySatisfy: [ :each | each > 60 ]). - self deny: (tree anySatisfy: [ :each | each > 100 ]) -] - -{ #category : 'tests' } -CTAVLTreeTest >> testAsArray [ - - tree addAll: #(50 30 70 20 40). - self assert: tree asArray equals: #(20 30 40 50 70) -] +CTAVLTreeTest >> testAddForLLrotation [ -{ #category : 'tests' } -CTAVLTreeTest >> testAtIfAbsent [ - - tree addAll: #(50 30 70). - self assert: (tree at: 30 ifAbsent: [ #notFound ]) equals: 30. - self assert: (tree at: 99 ifAbsent: [ #notFound ]) equals: #notFound -] - -{ #category : 'tests' } -CTAVLTreeTest >> testClear [ - - tree clear. - self assert: tree isEmpty. - self assert: tree size equals: 0 -] - -{ #category : 'tests' } -CTAVLTreeTest >> testCollect [ - - | doubled | - tree addAll: #(50 30 70 20 40). - - doubled := tree collect: [ :each | each * 2 ]. - self assert: doubled asArray equals: #(40 60 80 100 140) -] - -{ #category : 'tests' } -CTAVLTreeTest >> testComplexRotations [ - - "Test more complex rotation scenarios" - tree addAll: #(50 30 70 20 40 60 80 10). - self assert: tree validate. - - "Add more elements to trigger multiple rotations" - tree addAll: #(5 15 25 35 45). - self assert: tree validate. - - "Height should remain logarithmic" - self assert: tree height <= 5 -] - -{ #category : 'tests' } -CTAVLTreeTest >> testCopy [ - - | copiedTree | - tree addAll: #(50 30 70). - copiedTree := tree copy. - - self assert: copiedTree size equals: tree size. - self assert: copiedTree asArray equals: tree asArray. - copiedTree add: 99. - self deny: (tree includes: 99). - self assert: (copiedTree includes: 99) -] - -{ #category : 'tests' } -CTAVLTreeTest >> testDetect [ - - | found | - tree addAll: #(50 30 70 20 40). - - found := tree detect: [ :each | each > 45 ] ifNone: [ nil ]. - self assert: found equals: 50. - - found := tree detect: [ :each | each > 100 ] ifNone: [ #none ]. - self assert: found equals: #none + | data | + tree add: 3. + self assert: tree isBalanced. + tree add: 2. + self assert: tree isBalanced. + tree add: 1. + self assert: tree isBalanced. + data := tree collect: #yourself. + self assert: data asArray equals: { 1. 2. 3 } ] { #category : 'tests' } -CTAVLTreeTest >> testDoMethod [ +CTAVLTreeTest >> testAddForRRrotation [ - | result | - tree addAll: #(50 30 70 20 40). - - result := OrderedCollection new. - tree do: [ :each | result add: each ]. - - self assert: result asArray equals: #(20 30 40 50 70) + | data | + tree add: 1. + self assert: tree isBalanced. + tree add: 2. + self assert: tree isBalanced. + tree add: 3. + self assert: tree isBalanced. + data := tree collect: #yourself. + self assert: data asArray equals: { 1. 2. 3 } ] { #category : 'tests' } -CTAVLTreeTest >> testDuplicateHandling [ +CTAVLTreeTest >> testAddOneElement [ - tree addAll: #(42 42 50). - self assert: tree size equals: 2 + tree add: 1. + self assert: tree isNotEmpty. + self assert: (tree includes: 1) ] { #category : 'tests' } -CTAVLTreeTest >> testElementsFromTo [ +CTAVLTreeTest >> testAddTreeElements [ - | result | - tree addAll: #(50 30 70 20 40 60 80). - - result := tree elementsFrom: 35 to: 65. - self assert: result asArray equals: #(40 50 60). - - "Test edge cases" - result := tree elementsFrom: 10 to: 15. - self assert: result isEmpty. - - result := tree elementsFrom: 65 to: 35. "Invalid range" - self assert: result isEmpty + tree addAll: { 2. 4. 10 }. + self assert: (tree includes: 2). + self assert: (tree includes: 4). + self assert: (tree includes: 10) ] { #category : 'tests' } -CTAVLTreeTest >> testElementsGreaterThan [ +CTAVLTreeTest >> testAllChildren [ - | result | - tree addAll: #(50 30 70 20 40 60 80). - - result := tree elementsGreaterThan: 45. - self assert: result asArray equals: #(50 60 70 80). - - result := tree elementsGreaterThan: 80. - self assert: result isEmpty + | allChildren | + allChildren := tree allChildren. + self assert: allChildren isEmpty. + tree addAll: { 4. 2. 3. 10 }. + allChildren := tree allChildren. + self assert: allChildren size equals: 4. + self assert: allChildren first class equals: CTAVLNode ] { #category : 'tests' } -CTAVLTreeTest >> testElementsLessThan [ - - | result | - tree addAll: #(50 30 70 20 40 60 80). - - result := tree elementsLessThan: 45. - self assert: result asArray equals: #(20 30 40). - - result := tree elementsLessThan: 20. - self assert: result isEmpty +CTAVLTreeTest >> testDuplicateElement [ + tree add: 42. + "Adding the same element again should maintain balance" + tree add: 42. + self assert: tree isBalanced. + "Removing one instance of the element should keep the other" + tree remove: 42. + self assert: (tree includes: 42). + tree remove: 42. + self assert: tree isEmpty ] { #category : 'tests' } CTAVLTreeTest >> testEmpty [ - self assert: tree isEmpty. - self assert: tree size equals: 0. - self assert: tree height equals: 0 -] - -{ #category : 'tests' } -CTAVLTreeTest >> testEmptyTreeOperations [ - - self assert: tree findMin isNil. - self assert: tree findMax isNil. - self deny: (tree includes: 42). - - self deny: (tree anySatisfy: [ :each | true ]) -] - -{ #category : 'tests' } -CTAVLTreeTest >> testFindMinMax [ - - self assert: tree findMin isNil. - self assert: tree findMax isNil. - - tree addAll: #(50 30 70 20 80). - self assert: tree findMin equals: 20. - self assert: tree findMax equals: 80 -] - -{ #category : 'tests' } -CTAVLTreeTest >> testFindMinMaxNode [ - - tree addAll: #(50 30 70 20 80). - - self assert: tree findMinNode contents equals: 20. - self assert: tree findMaxNode contents equals: 80 -] - -{ #category : 'tests' } -CTAVLTreeTest >> testFirstLast [ - - tree addAll: #(50 30 70 20 80). - self assert: tree first equals: 20. - self assert: tree last equals: 80 + self assert: tree isEmpty ] { #category : 'tests' } CTAVLTreeTest >> testHeight [ self assert: tree height equals: 0. - - tree add: 50. + tree add: 4. self assert: tree height equals: 1. - - tree addAll: #(30 70). + tree add: 2. + self assert: tree height equals: 2. + tree add: 10. self assert: tree height equals: 2 ] { #category : 'tests' } -CTAVLTreeTest >> testIfEmptyIfNotEmpty [ +CTAVLTreeTest >> testIsBalanced [ - | result | - result := tree ifEmpty: [ #empty ]. - self assert: result equals: #empty. - - tree add: 42. - result := tree ifNotEmpty: [ :t | #notEmpty ]. - self assert: result equals: #notEmpty + self assert: tree isBalanced. + tree add: 4. + self assert: tree isBalanced. + tree add: 2. + self assert: tree isBalanced. + tree add: 10. + self assert: tree isBalanced ] { #category : 'tests' } -CTAVLTreeTest >> testInOrderTraversal [ +CTAVLTreeTest >> testIsLeaf [ - | result | - tree addAll: #(50 30 70 20 40). + self assert: tree isNil. + tree add: 1. + self assert: tree isLeaf. - result := OrderedCollection new. - tree inOrderDo: [ :each | result add: each ]. + tree add: 10. + self deny: tree isLeaf. - self assert: result asArray equals: #(20 30 40 50 70) + tree:= CTAVLTree new. + tree add: 10. + tree add: 5. + self deny: tree isLeaf. ] { #category : 'tests' } -CTAVLTreeTest >> testIncludes [ - - tree addAll: #(50 30 70 20 40). - - self assert: (tree includes: 50). - self assert: (tree includes: 30). - self deny: (tree includes: 99) +CTAVLTreeTest >> testLRRotation [ + "Test LR rotation" +|data| +tree := CTAVLTree new. +tree add: 1. +tree add: 3. +tree add: 2. +self assert: tree isBalanced. +data := tree collect: #yourself. +self assert: data asArray equals: { 1. 2. 3 }. ] { #category : 'tests' } -CTAVLTreeTest >> testIsLeaf [ +CTAVLTreeTest >> testRemove [ - tree add: 50. - self assert: tree root isLeaf. - - tree add: 30. - self deny: tree root isLeaf. - self assert: tree root left isLeaf + tree addAll: { 9. 4. 16. 7. 13. 19. 15 }. + tree add: 10. + tree add: 14. + tree add: 11. + tree remove: 13. + self assert: tree isTotalBalanced ] { #category : 'tests' } -CTAVLTreeTest >> testLLRotation [ +CTAVLTreeTest >> testRemoveBranchWithOneChild [ - tree add: 3. - tree add: 2. - tree add: 1. - - self assert: tree root contents equals: 2. - self assert: tree asArray equals: #(1 2 3) + tree addAll: { 9. 4. 16. 13. 15 }. + tree add: 14. + tree remove: 9. + self deny: (tree includes: 9). + self assert: (tree includes: 14) ] { #category : 'tests' } -CTAVLTreeTest >> testLRRotation [ +CTAVLTreeTest >> testRemoveBranchWithTwoChildren [ - tree add: 3. - tree add: 1. - tree add: 2. - - self assert: tree root contents equals: 2. - self assert: tree asArray equals: #(1 2 3) + tree addAll: { 9. 4. 16. 13. 15. 10 }. + tree add: 14. + tree remove: 15. + self deny: (tree includes: 15). + self assert: (tree includes: 14). + self assert: (tree includes: 16) ] { #category : 'tests' } -CTAVLTreeTest >> testNegativeNumbers [ +CTAVLTreeTest >> testRemoveBranchWithTwoChildren2 [ - tree addAll: #(-10 -5 0 5 10). - - self assert: tree findMin equals: -10. - self assert: tree findMax equals: 10. - self assert: tree asArray equals: #(-10 -5 0 5 10). - self assert: tree validate + tree addAll: { 9. 4. 16. 13. 15. 10. 14. 20 }. + tree remove: 15. + self deny: (tree includes: 15). + self assert: (tree includes: 20). + self assert: (tree includes: 14). + self assert: (tree includes: 16) ] { #category : 'tests' } -CTAVLTreeTest >> testParentChildRelationships [ +CTAVLTreeTest >> testRemoveBranchWithTwoChildren3 [ - tree addAll: #(50 30 70 20 40). - - self assert: tree root parent isNil. - self assert: tree root left parent equals: tree root. - self assert: tree root right parent equals: tree root + tree addAll: { 9. 4. 17. 13. 15. 10. 14. 20. 16 }. + tree remove: 15. + self deny: (tree includes: 15). + self assert: (tree includes: 20). + self assert: (tree includes: 14). + self assert: (tree includes: 16). + self assert: (tree includes: 17). + self assert: tree isBalanced ] { #category : 'tests' } -CTAVLTreeTest >> testPostOrderTraversal [ - - | result | - tree addAll: #(50 30 70 20). - result := OrderedCollection new. - tree postOrderDo: [ :each | result add: each ]. - self assert: result last equals: tree root contents +CTAVLTreeTest >> testRemoveFromEmptyTree [ + | executed | + executed := false. + tree remove: 42 ifAbsent: [ executed := true ]. + self assert: executed description: 'The ifAbsent: block should execute when removing from an empty tree'. + self assert: tree isEmpty description: 'Tree should remain empty after removal attempt'. ] { #category : 'tests' } -CTAVLTreeTest >> testPreOrderTraversal [ +CTAVLTreeTest >> testRemoveRootChildren [ - | result | - tree addAll: #(50 30 70 20). - result := OrderedCollection new. - tree preOrderDo: [ :each | result add: each ]. - self assert: result first equals: tree root contents + tree addAll: { 9 }. + tree remove: 9. + self assert: tree isEmpty ] { #category : 'tests' } -CTAVLTreeTest >> testPredecessorSuccessor [ +CTAVLTreeTest >> testRemoveRootChildren1 [ - tree addAll: #(50 30 70 20 40 60 80). - - self assert: (tree predecessorOf: 50) equals: 40. - self assert: (tree predecessorOf: 20) isNil. - self assert: (tree successorOf: 50) equals: 60. - self assert: (tree successorOf: 80) isNil. - - "Test with non-existent element" - self assert: (tree predecessorOf: 35) equals: 30. - self assert: (tree successorOf: 35) equals: 40 + tree addAll: { 9. 7. 10 }. + tree remove: 10. + self assert: tree size equals: 2. + self assert: (tree includes: 9). + self assert: (tree includes: 7) ] { #category : 'tests' } -CTAVLTreeTest >> testRLRotation [ +CTAVLTreeTest >> testRemoveRootChildren2 [ - tree add: 1. - tree add: 3. - tree add: 2. - - self assert: tree root contents equals: 2. - self assert: tree asArray equals: #(1 2 3) + tree addAll: { 9. 7 }. + tree remove: 7. + self assert: tree size equals: 1. + self assert: (tree includes: 9) ] { #category : 'tests' } -CTAVLTreeTest >> testRRRotation [ +CTAVLTreeTest >> testRemoveRootChildren3 [ - tree add: 1. - tree add: 2. - tree add: 3. - - self assert: tree root contents equals: 2. - self assert: tree asArray equals: #(1 2 3) + tree addAll: { 9. 7. 10 }. + tree remove: 9. + self assert: tree size equals: 2. + self deny: (tree includes: 9). + self assert: (tree includes: 10). + self assert: (tree includes: 7) ] { #category : 'tests' } -CTAVLTreeTest >> testRemovalCausingRebalancing [ +CTAVLTreeTest >> testRemoveZero [ - tree addAll: #(50 30 70 20 40 60 80 10 25). - - "Remove elements that should trigger rebalancing" - tree remove: 10. - tree remove: 20. - tree remove: 25. - - self assert: tree validate + | value | + value := false. + tree remove: 1 ifAbsent: [ value := true ]. + self assert: value ] { #category : 'tests' } -CTAVLTreeTest >> testRemoveAll [ +CTAVLTreeTest >> testSearch [ - tree addAll: #(50 30 70 20 40). - tree removeAll: #(30 70). - - self assert: tree size equals: 3. - self deny: (tree includes: 30). - self deny: (tree includes: 70). - self assert: tree validate + tree addAll: { 4. 2. 3. 10 }. + self assert: (tree search: 5) isNil. + self assert: (tree search: 4) equals: 4. + self assert: (tree search: 2) equals: 2. + self assert: (tree search: 10) equals: 10 ] { #category : 'tests' } -CTAVLTreeTest >> testRemoveLeafNode [ +CTAVLTreeTest >> testSeriousAdd [ - tree addAll: #(50 30 70 20). - - tree remove: 20. - self assert: tree size equals: 3. - self deny: (tree includes: 20). - self assert: tree validate + tree addAll: { 9. 4. 16. 7. 13. 19. 15 }. + self assert: tree isBalanced. + tree add: 10. + self assert: tree isBalanced. + tree add: 14. + self assert: tree isBalanced ] { #category : 'tests' } -CTAVLTreeTest >> testRemoveNodeWithOneChild [ +CTAVLTreeTest >> testSeriousRandomAdd [ - tree addAll: #(50 30 20). - tree remove: 30. - self assert: tree size equals: 2. - self deny: (tree includes: 30). - self assert: (tree includes: 20). - self assert: tree validate + | r size numbers | + r := Random new. + r seed: 1234. + size := 50. + numbers := (1 to: size) collect: [ :i | r nextInteger: size ]. + numbers := numbers asSet. + numbers do: [ :n | + tree add: n. + self assert: tree isBalanced ] ] { #category : 'tests' } -CTAVLTreeTest >> testRemoveNodeWithTwoChildren [ +CTAVLTreeTest >> testSeriousRandomRemove [ - tree addAll: #(50 30 70 20 40). - - tree remove: 30. - self assert: tree size equals: 4. - self deny: (tree includes: 30). - self assert: tree validate + | r size numbers toRemove | + r := Random new. + r seed: 1234. + size := 50. + numbers := (1 to: size) collect: [ :i | r nextInteger: size ]. + numbers := numbers asSet. + tree addAll: numbers. + self assert: tree isTotalBalanced. + toRemove := (1 to: 20) collect: [ :i | numbers atRandom: r ] as: Set. + toRemove do: [ :number | + tree remove: number. + self assert: tree isTotalBalanced ] ] { #category : 'tests' } -CTAVLTreeTest >> testRemoveNonExistentElement [ - - | result | - tree addAll: #(50 30 70). - result := tree remove: 99 ifAbsent: [ #notFound ]. - self assert: result equals: #notFound. - self assert: tree size equals: 3. - - self should: [ tree remove: 99 ] raise: Error +CTAVLTreeTest >> testSizeAfterRemoval [ + tree addAll: #(1 2 3 4 5). + tree remove: 3. + self assert: tree size equals: 4 ] { #category : 'tests' } -CTAVLTreeTest >> testRemoveRoot [ - - tree addAll: #(50 30 70 20 40 60 80). - tree remove: 50. - self assert: tree size equals: 6. - self deny: (tree includes: 50). - self assert: tree validate +CTAVLTreeTest >> testSizeEmpty [ + self assert: tree size equals: 0 ] { #category : 'tests' } -CTAVLTreeTest >> testReverseInsertions [ - - 15 to: 1 by: -1 do: [ :i | - tree add: i. - self assert: tree validate - ]. - - self assert: tree height <= 5. - self assert: tree asArray equals: (1 to: 15) asArray +CTAVLTreeTest >> testSizeMultipleElements [ + tree addAll: #(1 2 3 4 5). + self assert: tree size equals: 5 ] { #category : 'tests' } -CTAVLTreeTest >> testSelect [ - - | evens | - tree addAll: #(50 30 70 20 40). - - evens := tree select: [ :each | each even ]. - self assert: evens asArray equals: #(20 30 40 50 70) +CTAVLTreeTest >> testSizeOneElement [ + tree add: 42. + self assert: tree size equals: 1 ] { #category : 'tests' } -CTAVLTreeTest >> testSequentialInsertions [ - - "Test inserting elements in order (worst case for unbalanced BST)" - 1 to: 15 do: [ :i | - tree add: i. - self assert: tree validate - ]. +CTAVLTreeTest >> testChildParentRelationship [ + tree add: 50. + tree add: 30. + tree add: 70. - "Height should remain logarithmic" - self assert: tree height <= 5 + self assert: (tree root left parent) equals: tree root. + self assert: (tree root right parent) equals: tree root ] { #category : 'tests' } -CTAVLTreeTest >> testValidation [ - - self assert: tree validate. - - tree addAll: #(50 30 70 20 40). - self assert: tree validate. - - "Test after operations" - tree add: 60. - self assert: tree validate -] +CTAVLTreeTest >> testRootHasNoParent [ + tree add: 14. + self assert: tree root parent isNil +] \ No newline at end of file diff --git a/src/Containers-AVL-Tree/CTAVLAbstractNode.class.st b/src/Containers-AVL-Tree/CTAVLAbstractNode.class.st index c75646a..6361774 100644 --- a/src/Containers-AVL-Tree/CTAVLAbstractNode.class.st +++ b/src/Containers-AVL-Tree/CTAVLAbstractNode.class.st @@ -1,10 +1,16 @@ " -I am an abstract base class for AVL Tree nodes. -I define the common interface that both regular nodes (CTAVLNode) and nil nodes (CTAVLNilNode) must implement. -I include protocol for AVL-specific operations like height, balance factor, and rebalancing, as well as standard tree enumeration and searching. +AVLAbstractNode is an abstract class that represents a node in an AVL tree. + +AVL trees are self-balancing binary search trees. This abstract node class is designed to be subclassed by concrete AVL node classes. It provides common methods and instance variables that are essential for maintaining the balance of the AVL tree. + +Subclasses of AVLAbstractNode should implement methods for adding, removing, and manipulating nodes in the tree to ensure that the tree remains balanced. + Subclasses: -- CTAVLNilNode -- CTAVLNode +- AVLNilNode +- AVLNode + +Author: Milton Mamani +Date: October 20, 2023 " Class { #name : 'CTAVLAbstractNode', @@ -17,158 +23,71 @@ Class { } { #category : 'adding' } -CTAVLAbstractNode >> addChild: anObject [ - +CTAVLAbstractNode >> addChild: newObject [ ^ self subclassResponsibility ] -{ #category : 'accessing' } -CTAVLAbstractNode >> balanceFactor [ - - ^ self subclassResponsibility +{ #category : 'private' } +CTAVLAbstractNode >> checkRemovingPath: path [ + self subclassResponsibility ] { #category : 'accessing' } -CTAVLAbstractNode >> contents [ - - ^ self subclassResponsibility +CTAVLAbstractNode >> children [ + ^ #() ] { #category : 'accessing' } -CTAVLAbstractNode >> contents: anObject [ - - ^ self subclassResponsibility +CTAVLAbstractNode >> allChildren [ + ^ #() ] { #category : 'enumerating' } -CTAVLAbstractNode >> elementsFrom: min to: max into: aCollection [ - - ^ self subclassResponsibility - -] - -{ #category : 'enumerating' } -CTAVLAbstractNode >> elementsGreaterThan: anObject into: aCollection [ - +CTAVLAbstractNode >> childrenDo: aBlock [ ^ self subclassResponsibility ] { #category : 'enumerating' } -CTAVLAbstractNode >> elementsLessThan: anObject into: aCollection [ - - ^ self subclassResponsibility -] - -{ #category : 'searching' } -CTAVLAbstractNode >> findMax [ - - ^ self subclassResponsibility -] - -{ #category : 'searching' } -CTAVLAbstractNode >> findMaxNode [ - - ^ self subclassResponsibility -] - -{ #category : 'searching' } -CTAVLAbstractNode >> findMin [ - - ^ self subclassResponsibility -] - -{ #category : 'searching' } -CTAVLAbstractNode >> findMinNode [ - - ^ self subclassResponsibility +CTAVLAbstractNode >> do: aBlock [ + "Default is to do nothing" ] { #category : 'accessing' } CTAVLAbstractNode >> height [ - - ^ self subclassResponsibility -] - -{ #category : 'enumerating' } -CTAVLAbstractNode >> inOrderDo: aBlock [ - - ^ self subclassResponsibility + ^ 0 ] { #category : 'testing' } -CTAVLAbstractNode >> isEmpty [ - - ^ self subclassResponsibility +CTAVLAbstractNode >> isBalanced [ + ^ true ] { #category : 'testing' } -CTAVLAbstractNode >> isLeaf [ - - ^ self subclassResponsibility -] - -{ #category : 'accessing' } -CTAVLAbstractNode >> parent [ - - ^ parent +CTAVLAbstractNode >> isTotalBalanced [ + ^ true ] { #category : 'accessing' } -CTAVLAbstractNode >> parent: aNode [ - - parent := aNode -] - -{ #category : 'enumerating' } -CTAVLAbstractNode >> postOrderDo: aBlock [ - - ^ self subclassResponsibility -] - -{ #category : 'enumerating' } -CTAVLAbstractNode >> preOrderDo: aBlock [ - - ^ self subclassResponsibility -] - -{ #category : 'searching' } -CTAVLAbstractNode >> predecessorOf: anObject [ - - ^ self subclassResponsibility +CTAVLAbstractNode >> nodeSize [ + ^ self subclassResponsibility ] { #category : 'removing' } -CTAVLAbstractNode >> removeValue: anObject [ - - ^ self subclassResponsibility +CTAVLAbstractNode >> remove: anObject path: list [ + ^ nil ] { #category : 'accessing' } -CTAVLAbstractNode >> search: anObject [ - - ^ self subclassResponsibility +CTAVLAbstractNode >> withAllChildren: aCollection [ + "Default is to do nothing" ] { #category : 'accessing' } -CTAVLAbstractNode >> size [ - - ^ self subclassResponsibility -] - -{ #category : 'searching' } -CTAVLAbstractNode >> successorOf: anObject [ - - ^ self subclassResponsibility -] - -{ #category : 'validation' } -CTAVLAbstractNode >> validateAsRoot [ - - ^ self subclassResponsibility +CTAVLAbstractNode >> parent [ + ^ parent ] -{ #category : 'validation' } -CTAVLAbstractNode >> validateWithMin: min max: max [ - - ^ self subclassResponsibility -] +{ #category : 'accessing' } +CTAVLAbstractNode >> parent: aNode [ + parent := aNode +] \ No newline at end of file diff --git a/src/Containers-AVL-Tree/CTAVLNilNode.class.st b/src/Containers-AVL-Tree/CTAVLNilNode.class.st index 1ac49a8..1668f45 100644 --- a/src/Containers-AVL-Tree/CTAVLNilNode.class.st +++ b/src/Containers-AVL-Tree/CTAVLNilNode.class.st @@ -1,7 +1,10 @@ " -I represent an empty node in an AVL Tree using the Null Object pattern. -I provide default 'do nothing' behavior for all tree operations, eliminating the need for nil checks throughout the tree algorithms. This makes the code cleaner and prevents null pointer errors. -When elements are added to me, I create and return a new CTAVLNode containing the element, effectively growing the tree. I maintain height 0 and am always balanced +AVLNilNode is a special sentinel node used in AVL trees to represent the absence of a node. + +In an AVL tree, `AVLNilNode` is used as a placeholder for null references, making it easier to perform tree operations without special cases for missing children. It fully implements the Null Object pattern. + +Author: Milton Mamani +Date: October 20, 2023 " Class { #name : 'CTAVLNilNode', @@ -11,147 +14,61 @@ Class { } { #category : 'adding' } -CTAVLNilNode >> addChild: anObject [ - - ^ CTAVLNode new - contents: anObject; - parent: self parent; - yourself +CTAVLNilNode >> addChild: newObject [ + ^ self parent replace: self with: (CTAVLNode with: newObject) ] -{ #category : 'accessing' } -CTAVLNilNode >> balanceFactor [ - - ^ 0 +{ #category : 'private' } +CTAVLNilNode >> checkRemovingPath: path [ + "Do nothing for nil node" ] { #category : 'accessing' } -CTAVLNilNode >> contents [ - - ^ nil +CTAVLNilNode >> children [ + ^ #() ] { #category : 'accessing' } -CTAVLNilNode >> contents: anObject [ - - "Do nothing for nil node" +CTAVLNilNode >> allChildren [ + ^ #() ] { #category : 'enumerating' } -CTAVLNilNode >> elementsFrom: min to: max into: aCollection [ - - "Do Nothing for Nil Node" -] - -{ #category : 'enumerating' } -CTAVLNilNode >> elementsGreaterThan: anObject into: aCollection [ - - "Do Nothing for Nil Node" +CTAVLNilNode >> childrenDo: aBlock [ + "Do nothing for nil node" ] { #category : 'enumerating' } -CTAVLNilNode >> elementsLessThan: anObject into: aCollection [ - - "Do Nothing for Nil Node" -] - -{ #category : 'searching' } -CTAVLNilNode >> findMax [ - - ^ nil -] - -{ #category : 'searching' } -CTAVLNilNode >> findMaxNode [ - - ^ self -] - -{ #category : 'searching' } -CTAVLNilNode >> findMin [ - - ^ nil -] - -{ #category : 'searching' } -CTAVLNilNode >> findMinNode [ - - ^ self +CTAVLNilNode >> do: aBlock [ + "Do nothing for nil node" ] { #category : 'accessing' } -CTAVLNilNode >> height [ - +CTAVLNilNode >> height [ ^ 0 ] -{ #category : 'enumerating' } -CTAVLNilNode >> inOrderDo: aBlock [ - - "Do Nothing for Nil Node" -] - { #category : 'testing' } -CTAVLNilNode >> isEmpty [ - +CTAVLNilNode >> isBalanced [ ^ true ] { #category : 'testing' } -CTAVLNilNode >> isLeaf [ - ^ false -] - -{ #category : 'enumerating' } -CTAVLNilNode >> postOrderDo: aBlock [ - - "Do Nothing for Nil Node" -] - -{ #category : 'enumerating' } -CTAVLNilNode >> preOrderDo: aBlock [ - - "Do Nothing for Nil Node" -] - -{ #category : 'searching' } -CTAVLNilNode >> predecessorOf: anObject [ - - ^ nil -] - -{ #category : 'removing' } -CTAVLNilNode >> removeValue: anObject [ - - ^ self -] - -{ #category : 'accessing' } -CTAVLNilNode >> search: anObject [ - - ^ nil +CTAVLNilNode >> isTotalBalanced [ + ^ true ] { #category : 'accessing' } -CTAVLNilNode >> size [ - +CTAVLNilNode >> nodeSize [ ^ 0 ] -{ #category : 'searching' } -CTAVLNilNode >> successorOf: anObject [ - +{ #category : 'removing' } +CTAVLNilNode >> remove: anObject path: list [ ^ nil ] -{ #category : 'validation' } -CTAVLNilNode >> validateAsRoot [ - - ^ true -] - -{ #category : 'validation' } -CTAVLNilNode >> validateWithMin: min max: max [ - - ^ true -] +{ #category : 'accessing' } +CTAVLNilNode >> withAllChildren: aCollection [ + "Do nothing for nil node" +] \ No newline at end of file diff --git a/src/Containers-AVL-Tree/CTAVLNode.class.st b/src/Containers-AVL-Tree/CTAVLNode.class.st index 484f299..a7c682f 100644 --- a/src/Containers-AVL-Tree/CTAVLNode.class.st +++ b/src/Containers-AVL-Tree/CTAVLNode.class.st @@ -1,7 +1,19 @@ " -I represent a node in an AVL Tree. -I extend the functionality of a standard BST node by storing my height and performing self-balancing rotations after any modification (add or remove) to ensure the tree's height balance is maintained. -The AVL invariant I maintain is: |height(left) - height(right)| <= 1 +AVLNode represents a node in an AVL tree. + +An AVL tree is a self-balancing binary search tree where the heights of the two child subtrees of every node differ by at most one. The `AVLNode` class extends the `AVLAbstractNode` class and provides the implementation of actual nodes within the AVL tree. + +AVLNode instances hold a `contents` instance variable. These nodes are organized to keep the tree balanced, ensuring efficient operations like insertion, deletion, and search. + +This class should not be used directly; instead use `AVLTree`. + +Instance Variables: +- contents: The value associated with this node. +- left: The left child node. +- right: The right child node. + +Author: Milton Mamani +Date: October 20, 2023 " Class { #name : 'CTAVLNode', @@ -9,8 +21,7 @@ Class { #instVars : [ 'left', 'contents', - 'right', - 'height' + 'right' ], #category : 'Containers-AVL-Tree', #package : 'Containers-AVL-Tree' @@ -26,305 +37,299 @@ CTAVLNode class >> with: anInteger [ ] { #category : 'adding' } -CTAVLNode >> addChild: anObject [ - - anObject < contents - ifTrue: [ self left: (left addChild: anObject) ] - ifFalse: [ - anObject > contents - ifTrue: [ self right: (right addChild: anObject) ] ]. - ^ self updateHeightAndRebalance +CTAVLNode >> add: anInteger path: list [ + anInteger < contents ifTrue: [ + list add: false -> left. + left := left addChild: anInteger. + ] ifFalse: [ + list add: true -> right. + right := right addChild: anInteger. + ] ] -{ #category : 'accessing' } -CTAVLNode >> balanceFactor [ - - ^ left height - right height +{ #category : 'adding' } +CTAVLNode >> addChild: newObject [ + | path | + path := OrderedCollection with: nil -> self. + self add: newObject path: path. + self checkPath: path. + ^ self ] -{ #category : 'accessing' } -CTAVLNode >> contents [ - - ^ contents +{ #category : 'private' } +CTAVLNode >> balance: index path: aCollection [ + | x y z a b c | + z := aCollection at: index. + y := aCollection at: index + 1. + x := aCollection at: index + 2. + c := z value. + b := y value. + a := x value. + (y key and: [ x key ]) ifTrue: [ + ^ self rrrotationZ: c y: b x: a ]. + (y key not and: [ x key not ]) ifTrue: [ + ^ self llrotationZ: c y: b x: a ]. + (y key not and: [ x key ]) ifTrue: [ + ^ self lrrotationZ: c y: b x: a ]. + "(y key and: [ x key not ])" + ^ self rlrotationZ: c y: b x: a. ] -{ #category : 'accessing' } -CTAVLNode >> contents: anObject [ - - contents := anObject +{ #category : 'private' } +CTAVLNode >> balanceZ: z y: y x: x [ + | a b c | + c := z value. + b := y value. + a := x value. + (y key and: [ x key ]) ifTrue: [ + ^ self rrrotationZ: c y: b x: a ]. + (y key not and: [ x key not ]) ifTrue: [ + ^ self llrotationZ: c y: b x: a ]. + (y key not and: [ x key ]) ifTrue: [ + ^ self lrrotationZ: c y: b x: a ]. + "(y key and: [ x key not ])" + ^ self rlrotationZ: c y: b x: a. ] -{ #category : 'enumerating' } -CTAVLNode >> elementsFrom: min to: max into: aCollection [ +{ #category : 'private' } +CTAVLNode >> checkPath: aCollection [ + aCollection size < 3 ifTrue: [ ^ self ]. + (1 to: aCollection size - 2) reverseDo: [ :index | + | assoc | + assoc := aCollection at: index. + assoc value isBalanced ifFalse: [ | z y x | + z := aCollection at: index. + y := aCollection at: index + 1. + x := aCollection at: index + 2. + ^ self balanceZ: z y: y x: x ] ] +] - contents > min ifTrue: [ - left elementsFrom: min to: max into: aCollection ]. - (contents between: min and: max) ifTrue: [ aCollection add: contents ]. - contents < max ifTrue: [ - right elementsFrom: min to: max into: aCollection ] +{ #category : 'private' } +CTAVLNode >> checkRemovingPath: path [ + path reverseDo: [ :node | + node isBalanced ifFalse: [ | z y x | + z := node. + y := z largerNode. + x := y value largerNode. + self balanceZ: node y: y x: x ] ]. ] -{ #category : 'enumerating' } -CTAVLNode >> elementsGreaterThan: anObject into: aCollection [ +{ #category : 'accessing' } +CTAVLNode >> children [ + ^ { left. right } +] - contents > anObject ifTrue: [ - left elementsGreaterThan: anObject into: aCollection. - aCollection add: contents ]. - right elementsGreaterThan: anObject into: aCollection +{ #category : 'accessing' } +CTAVLNode >> allChildren [ + "Collects all child nodes recursively." + | children | + children := OrderedCollection new. + self left ifNotNil: [children addAll: self left allChildren]. + self right ifNotNil: [children addAll: self right allChildren]. + children add: self. + ^ children. ] { #category : 'enumerating' } -CTAVLNode >> elementsLessThan: anObject into: aCollection [ - - left elementsLessThan: anObject into: aCollection. - contents < anObject ifFalse: [ ^ self ]. - aCollection add: contents. - right elementsLessThan: anObject into: aCollection +CTAVLNode >> childrenDo: aBlock [ + left childrenDo: aBlock. + right childrenDo: aBlock. ] -{ #category : 'searching' } -CTAVLNode >> findMax [ - - ^ right isEmpty - ifTrue: [ contents ] - ifFalse: [ right findMax ] +{ #category : 'accessing' } +CTAVLNode >> contents [ + ^ contents ] -{ #category : 'searching' } -CTAVLNode >> findMaxNode [ - - ^ right isEmpty - ifTrue: [ self ] - ifFalse: [ right findMaxNode ] +{ #category : 'accessing' } +CTAVLNode >> contents: anInteger [ + contents := anInteger ] -{ #category : 'searching' } -CTAVLNode >> findMin [ - - ^ left isEmpty - ifTrue: [ contents ] - ifFalse: [ left findMin ] +{ #category : 'enumerating' } +CTAVLNode >> do: aBlock [ + left do: aBlock. + aBlock value: self contents. + right do: aBlock. ] -{ #category : 'searching' } -CTAVLNode >> findMinNode [ - - ^ left isEmpty - ifTrue: [ self ] - ifFalse: [ left findMinNode ] +{ #category : 'testing' } +CTAVLNode >> hasNoChildren [ + ^ left nodeSize = 0 and: [ right nodeSize = 0 ] ] { #category : 'accessing' } CTAVLNode >> height [ - - ^ height + ^ (left height max: right height) + 1 ] -{ #category : 'enumerating' } -CTAVLNode >> inOrderDo: aBlock [ - - left inOrderDo: aBlock. - aBlock value: contents. - right inOrderDo: aBlock -] - -{ #category : 'initialization' } -CTAVLNode >> initialize [ - - super initialize. - left := CTAVLNilNode new parent: self. - right := CTAVLNilNode new parent: self. - height := 1 +{ #category : 'testing' } +CTAVLNode >> isBalanced [ + ^ (left height - right height) abs <= 1 ] { #category : 'testing' } -CTAVLNode >> isEmpty [ - - ^ false +CTAVLNode >> isTotalBalanced [ + ^ self isBalanced + and: [ left isTotalBalanced + and: [ right isTotalBalanced ] ] ] -{ #category : 'testing' } -CTAVLNode >> isLeaf [ - ^ left isEmpty and: [ right isEmpty ] +{ #category : 'accessing' } +CTAVLNode >> largerNode [ + | size1 size2 isLeft | + size1 := left height. + size2 := right height. + isLeft := size1 > size2. + ^ isLeft not -> (isLeft ifTrue: [ left ] ifFalse: [ right ]) ] { #category : 'accessing' } CTAVLNode >> left [ - ^ left ] { #category : 'accessing' } -CTAVLNode >> left: aNode [ - +CTAVLNode >> left: aNode [ left := aNode. - aNode ifNotNil: [ aNode parent: self ] + aNode parent: self ] -{ #category : 'enumerating' } -CTAVLNode >> postOrderDo: aBlock [ - - left postOrderDo: aBlock. - right postOrderDo: aBlock. - aBlock value: contents +{ #category : 'private' } +CTAVLNode >> llrotationZ: z y: y x: x [ + | a3 a4 new | + a3 := y right. + a4 := z right. + + new := self class with: z contents. + new left: a3; right: a4. + z left: x; contents: y contents; right: new. ] -{ #category : 'enumerating' } -CTAVLNode >> preOrderDo: aBlock [ - - aBlock value: contents. - left preOrderDo: aBlock. - right preOrderDo: aBlock +{ #category : 'private' } +CTAVLNode >> lrrotationZ: z y: y x: x [ + | a1 a2 a3 new | + a1 := y left. + a2 := x left. + a3 := x right. + new := self class with: y contents. + new left: a1; right: a2. + y contents: x contents; left: new; right: a3. + + self llrotationZ: z y: y x: new ] -{ #category : 'searching' } -CTAVLNode >> predecessorOf: anObject [ - - | rightResult | - anObject <= contents ifTrue: [ - ^ left predecessorOf: anObject ]. - rightResult := right predecessorOf: anObject. - ^ rightResult ifNil: [ contents ] ifNotNil: [ rightResult ] +{ #category : 'accessing' } +CTAVLNode >> nodeSize [ + ^ 1 + left nodeSize + right nodeSize ] -{ #category : 'private' } -CTAVLNode >> rebalance [ - - | bf | - bf := self balanceFactor. - "Left-heavy case" - bf > 1 ifTrue: [ - "Left-Right case" - (left balanceFactor < 0) ifTrue: [ self left: left rotateLeft ]. - ^ self rotateRight ]. - "Right-heavy case" - bf < -1 ifTrue: [ - "Right-Left case" - (right balanceFactor > 0) ifTrue: [ self right: right rotateRight ]. - ^ self rotateLeft ]. - ^ self "Already balanced" +{ #category : 'printing' } +CTAVLNode >> printOn: stream [ + contents printOn: stream ] -{ #category : 'private' } -CTAVLNode >> removeThisNode [ - - "Handle removal of the current node, then rebalance." - | successorValue | - "Case 1 & 2: Leaf or one child" - left isEmpty ifTrue: [ ^ right ]. - right isEmpty ifTrue: [ ^ left ]. - "Case 3: Two children" - successorValue := right findMin. - self contents: successorValue. - self right: (right removeValue: successorValue). - ^ self updateHeightAndRebalance +{ #category : 'removing' } +CTAVLNode >> remove: anObject path: list [ + contents = anObject ifTrue: [ + ^ self + ] ifFalse: [ + | node nodeToRemove isLeft | + isLeft := anObject < contents. + node := isLeft ifTrue: [ left ] ifFalse: [ right ]. + list add: self. + nodeToRemove := node remove: anObject path: list. + nodeToRemove == node ifTrue: [ + | successor | + successor := node successor: list. + isLeft + ifTrue: [ left := successor ] + ifFalse: [ right := successor ] + ]. + ^ nodeToRemove + ]. ] { #category : 'removing' } -CTAVLNode >> removeValue: anObject [ - - anObject < contents ifTrue: [ - self left: (left removeValue: anObject). - ^ self updateHeightAndRebalance ]. - anObject > contents ifTrue: [ - self right: (right removeValue: anObject). - ^ self updateHeightAndRebalance ]. - "Found the node to remove" - ^ self removeThisNode +CTAVLNode >> removeMinimum: list [ + | res | + left nodeSize = 0 ifTrue: [ + res := self class with: contents. + contents := right contents. + left := right left. + right := right right ] + ifFalse: [ + list add: self. + left hasNoChildren ifTrue: [ + res := left. + left := CTAVLNilNode new ] + ifFalse: [ res := left removeMinimum: list ] ]. + ^ res ] { #category : 'accessing' } CTAVLNode >> right [ - ^ right ] { #category : 'accessing' } -CTAVLNode >> right: aNode [ - +CTAVLNode >> right: aNode [ right := aNode. - aNode ifNotNil: [ aNode parent: self ] + aNode parent: self ] { #category : 'private' } -CTAVLNode >> rotateLeft [ - - | newRoot | - newRoot := right. - self right: newRoot left. - newRoot left: self. - self updateHeight. - newRoot updateHeight. - ^ newRoot +CTAVLNode >> rlrotationZ: z y: y x: x [ + | a1 a2 a3 a4 new | + a1 := z left. + a2 := x left. + a3 := x right. + a4 := y right. + new := self class with: y contents. + new left: a3; right: a4. + y contents: x contents; left: a2; right: new. + self rrrotationZ: z y: y x: new ] { #category : 'private' } -CTAVLNode >> rotateRight [ - - | newRoot | - newRoot := left. - self left: newRoot right. - newRoot right: self. - self updateHeight. - newRoot updateHeight. - ^ newRoot +CTAVLNode >> rrrotationZ: z y: y x: x [ + | a1 a2 new | + a1 := z left. + a2 := y left. + + new := self class with: z contents. + new left: a1; right: a2. + z left: new; right: x; contents: y contents ] -{ #category : 'accessing' } -CTAVLNode >> search: anObject [ - - contents = anObject ifTrue: [ ^ contents ]. - ^ anObject < contents - ifTrue: [ left search: anObject ] - ifFalse: [ right search: anObject ] -] - -{ #category : 'accessing' } -CTAVLNode >> size [ - - ^ 1 + left size + right size +{ #category : 'search' } +CTAVLNode >> search: anInteger [ + contents = anInteger ifTrue: [ ^ contents ]. + ^ (anInteger < contents ifTrue: [ left ] ifFalse: [ right ]) search: anInteger ] -{ #category : 'searching' } -CTAVLNode >> successorOf: anObject [ - - | leftResult | - anObject >= contents ifTrue: [ - ^ right successorOf: anObject ]. - leftResult := left successorOf: anObject. - ^ leftResult ifNil: [ contents ] ifNotNil: [ leftResult ] -] - -{ #category : 'private' } -CTAVLNode >> updateHeight [ - -height := 1 + (left height max: right height) -] - -{ #category : 'private' } -CTAVLNode >> updateHeightAndRebalance [ - - self updateHeight. - ^ self rebalance -] - -{ #category : 'validation' } -CTAVLNode >> validateAsRoot [ - - ^ self validateWithMin: nil max: nil +{ #category : 'removing' } +CTAVLNode >> successor: list [ + ^ self hasNoChildren + ifTrue: [ CTAVLNilNode new ] + ifFalse: [ + (left nodeSize > 0 and: [ right nodeSize > 0 ]) ifTrue: [ + right hasNoChildren + ifTrue: [ list add: (right left: left; yourself) ] + ifFalse: [ | min newList | + newList := OrderedCollection new. + min := right removeMinimum: newList. + list add: min. + list addAll: newList. + min left: left. + min right: right. + right := min ] + ] ifFalse: [ + list add: (left nodeSize = 0 ifTrue: [ right ] ifFalse: [ left ]) ] ] ] -{ #category : 'validation' } -CTAVLNode >> validateWithMin: min max: max [ - - "Check BST property" - (min notNil and: [ contents < min ]) ifTrue: [ ^ false ]. - (max notNil and: [ contents > max ]) ifTrue: [ ^ false ]. - "Check AVL property" - (self balanceFactor abs > 1) ifTrue: [ ^ false ]. - "Check height property" - (self height = (1 + (left height max: right height))) ifFalse: [ ^ false ]. - "Recursively validate children" - ^ (left validateWithMin: min max: contents) and: [ - right validateWithMin: contents max: max ] -] +{ #category : 'accessing' } +CTAVLNode >> withAllChildren: aCollection [ + aCollection add: self. + self childrenDo: [ :child | child withAllChildren: aCollection ] +] \ No newline at end of file diff --git a/src/Containers-AVL-Tree/CTAVLTree.class.st b/src/Containers-AVL-Tree/CTAVLTree.class.st index 155a8a0..705a8be 100644 --- a/src/Containers-AVL-Tree/CTAVLTree.class.st +++ b/src/Containers-AVL-Tree/CTAVLTree.class.st @@ -1,19 +1,29 @@ " -I represent an AVL Tree, a self-balancing binary search tree. -I maintain the BST property (left < node < right) while also ensuring that the height difference between the left and right subtrees of any node is at most 1. This balancing act guarantees O(log n) worst-case performance for all primary operations. -My public API is designed to be polymorphic with CTBinarySearchTree and includes a rich set of collection methods for enumeration, searching, and accessing. +AVLTree is an implementation of a self-balancing AVL (Adelson-Velsky and Landis) binary search tree. + +An AVL tree is a binary search tree in which the heights of the two child subtrees of every node differ by at most one. This self-balancing property ensures that the tree remains approximately balanced, leading to efficient insertion, deletion, and search operations. The instances of `AVLTree` uses AVLNode to manage the overall structure of the AVL tree =, + +`AVLTree` is a generic tree that can store comparable objects. It uses `AVLNode` instances to represent the nodes within the tree. + Usage: - tree := CTAVLTree new. - tree addAll: #(50 30 70 20 40 60 80). - tree asArray. => #(20 30 40 50 60 70 80) - tree height. => 3 - tree first. => 20 - tree includes: 30. => true - tree elementsFrom: 30 to: 60. => #(30 40 50 60) + To use `AVLTree`, create an instance of the class and then use the provided methods to insert, remove, or search for elements within the tree. The tree will automatically self-balance as elements are added or removed. + +Example: +``` +| tree | +tree := AVLTree new. +tree add: 41. +tree add: 87. +tree add: 20. +tree remove: 87. +tree +``` +Author: Milton +Date: October 20, 2023 " Class { #name : 'CTAVLTree', - #superclass : 'Object', + #superclass : 'Collection', #instVars : [ 'root' ], @@ -22,276 +32,80 @@ Class { } { #category : 'adding' } -CTAVLTree >> add: anObject [ - - root := root addChild: anObject. - root parent: nil. - ^ anObject -] - -{ #category : 'adding' } -CTAVLTree >> addAll: aCollection [ - - aCollection do: [ :each | self add: each ]. - ^ aCollection +CTAVLTree >> add: newObject [ + root := root addChild: newObject. + ^ newObject ] { #category : 'enumerating' } -CTAVLTree >> anySatisfy: aBlock [ - - self inOrderDo: [ :each | - (aBlock value: each) ifTrue: [ ^ true ] - ]. - ^ false -] - -{ #category : 'converting' } -CTAVLTree >> asArray [ - - | result | - result := OrderedCollection new: self size. - self inOrderDo: [ :each | result add: each ]. - ^ result asArray -] - -{ #category : 'accessing' } -CTAVLTree >> at: anObject ifAbsent: aBlock [ - - | result | - result := root search: anObject. - ^ result ifNil: [ aBlock value ] ifNotNil: [ result ] -] - -{ #category : 'removing' } -CTAVLTree >> clear [ - - root := CTAVLNilNode new -] - -{ #category : 'enumerating' } -CTAVLTree >> collect: aBlock [ - - | result | - result := OrderedCollection new: self size. - self inOrderDo: [ :each | result add: (aBlock value: each) ]. - ^ result -] - -{ #category : 'copying' } -CTAVLTree >> copy [ - - | newTree | - newTree := self class new. - self inOrderDo: [ :each | newTree add: each ]. - ^ newTree -] - -{ #category : 'enumerating' } -CTAVLTree >> detect: aBlock ifNone: absentBlock [ - - self inOrderDo: [ :each | - (aBlock value: each) ifTrue: [ ^ each ] - ]. - ^ absentBlock value -] - -{ #category : 'enumerating' } -CTAVLTree >> do: aBlock [ - - "Alias for inOrderDo: - visits elements in sorted order" - self inOrderDo: aBlock -] - -{ #category : 'enumerating' } -CTAVLTree >> elementsFrom: min to: max [ - - | result | - min > max ifTrue: [ ^ #() ]. - result := OrderedCollection new. - root elementsFrom: min to: max into: result. - ^ result -] - -{ #category : 'enumerating' } -CTAVLTree >> elementsGreaterThan: anObject [ - - | result | - result := OrderedCollection new. - root elementsGreaterThan: anObject into: result. - ^ result -] - -{ #category : 'enumerating' } -CTAVLTree >> elementsLessThan: anObject [ - - | result | - result := OrderedCollection new. - root elementsLessThan: anObject into: result. - ^ result -] - -{ #category : 'searching' } -CTAVLTree >> findMax [ - - ^ self isEmpty - ifTrue: [ nil ] - ifFalse: [ root findMax ] -] - -{ #category : 'searching' } -CTAVLTree >> findMaxNode [ - - ^ self isEmpty - ifTrue: [ nil ] - ifFalse: [ root findMaxNode ] -] - -{ #category : 'searching' } -CTAVLTree >> findMin [ - - ^ self isEmpty - ifTrue: [ nil ] - ifFalse: [ root findMin ] -] - -{ #category : 'searching' } -CTAVLTree >> findMinNode [ - - ^ self isEmpty - ifTrue: [ nil ] - ifFalse: [ root findMinNode ] +CTAVLTree >> do: aBlock [ + root do: aBlock ] -{ #category : 'accessing' } -CTAVLTree >> first [ - - ^ self findMin +{ #category : 'testing' } +CTAVLTree >> hasNoChildren [ + ^ root nodeSize = 0 ] -{ #category : 'accessing' } +{ #category : 'public' } CTAVLTree >> height [ - ^ root height ] -{ #category : 'testing' } -CTAVLTree >> ifEmpty: aBlock [ - - ^ self isEmpty - ifTrue: [ aBlock value ] - ifFalse: [ self ] -] - -{ #category : 'testing' } -CTAVLTree >> ifNotEmpty: aBlock [ - - ^ self isEmpty - ifFalse: [ aBlock value: self ] - ifTrue: [ self ] -] - -{ #category : 'enumerating' } -CTAVLTree >> inOrderDo: aBlock [ - - root inOrderDo: aBlock -] - { #category : 'testing' } CTAVLTree >> includes: anObject [ - - ^ (root search: anObject) notNil + anObject ifNil: [ ^ false ]. + ^ (self search: anObject) notNil ] { #category : 'initialization' } CTAVLTree >> initialize [ - super initialize. - root := CTAVLNilNode new + root := CTAVLNilNode new. ] { #category : 'testing' } -CTAVLTree >> isEmpty [ - - ^ root isEmpty +CTAVLTree >> isBalanced [ + ^ root isBalanced ] -{ #category : 'accessing' } -CTAVLTree >> last [ - - ^ self findMax -] - -{ #category : 'enumerating' } -CTAVLTree >> postOrderDo: aBlock [ - - root postOrderDo: aBlock -] - -{ #category : 'enumerating' } -CTAVLTree >> preOrderDo: aBlock [ - - root preOrderDo: aBlock -] - -{ #category : 'searching' } -CTAVLTree >> predecessorOf: anObject [ - - ^ root predecessorOf: anObject -] - -{ #category : 'removing' } -CTAVLTree >> remove: anObject [ - - ^ self - remove: anObject - ifAbsent: [ NotFound signalFor: anObject in: self ] +{ #category : 'testing' } +CTAVLTree >> isTotalBalanced [ + ^ root isTotalBalanced ] { #category : 'removing' } -CTAVLTree >> remove: anObject ifAbsent: aBlock [ - - (self includes: anObject) ifFalse: [ ^ aBlock value ]. - root := root removeValue: anObject. - root parent: nil. - ^ anObject +CTAVLTree >> remove: oldObject ifAbsent: anExceptionBlock [ + | toRemove path | + path := OrderedCollection new. + toRemove := root remove: oldObject path: path. + toRemove ifNil: [ ^ anExceptionBlock value ]. + + toRemove == root ifTrue: [ + root := root successor: path. + root ifNil: [ root := CTAVLNilNode new ] ]. + root checkRemovingPath: path. + + ^ toRemove contents ] -{ #category : 'removing' } -CTAVLTree >> removeAll: aCollection [ - - aCollection do: [ :each | self remove: each ifAbsent: [ ] ]. - ^ aCollection +{ #category : 'search' } +CTAVLTree >> search: anInteger [ + ^ root search: anInteger ] { #category : 'accessing' } -CTAVLTree >> root [ - - ^ root isEmpty ifTrue: [ nil ] ifFalse: [ root ] -] - -{ #category : 'enumerating' } -CTAVLTree >> select: aBlock [ - - | result | - result := OrderedCollection new. - self inOrderDo: [ :each | (aBlock value: each) ifTrue: [ result add: each ] ]. - ^ result +CTAVLTree >> size [ + ^ root nodeSize ] { #category : 'accessing' } -CTAVLTree >> size [ - ^ root size -] - -{ #category : 'searching' } -CTAVLTree >> successorOf: anObject [ - - ^ root successorOf: anObject +CTAVLTree >> root [ + ^ root ] { #category : 'accessing' } -CTAVLTree >> validate [ - - "Validate that the tree maintains both BST and AVL properties" - ^ root validateAsRoot -] +CTAVLTree >> allChildren [ + "Returns a collection of all child nodes in the tree." + ^ root ifNil: [#()] ifNotNil: [root allChildren]. +] \ No newline at end of file