diff --git a/src/Containers-BinarySearchTree-Tests/CTBinarySearchTreeTest.class.st b/src/Containers-BinarySearchTree-Tests/CTBinarySearchTreeTest.class.st index df28544..5cea465 100644 --- a/src/Containers-BinarySearchTree-Tests/CTBinarySearchTreeTest.class.st +++ b/src/Containers-BinarySearchTree-Tests/CTBinarySearchTreeTest.class.st @@ -48,6 +48,14 @@ CTBinarySearchTreeTest >> testAsArray [ self assert: result equals: #(20 30 40 50 70) ] +{ #category : 'tests' } +CTBinarySearchTreeTest >> 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' } CTBinarySearchTreeTest >> testBSTValidation [ @@ -186,12 +194,13 @@ CTBinarySearchTreeTest >> testEmptyTreeOperations [ ] { #category : 'tests' } -CTBinarySearchTreeTest >> testFindMinMax [ +CTBinarySearchTreeTest >> testFirstLast [ tree addAll: #(50 30 70 20 80). - - self assert: tree findMin equals: 20. - self assert: tree findMax equals: 80 + self assert: tree first equals: 20. + self assert: tree last equals: 80. + self assert: CTBinarySearchTree new first isNil. + self assert: CTBinarySearchTree new last isNil ] { #category : 'tests' } @@ -362,16 +371,20 @@ CTBinarySearchTreeTest >> testRemoveNonExistentElement [ { #category : 'tests' } CTBinarySearchTreeTest >> testRemoveRoot [ "Root with no children" + tree add: 50. tree remove: 50. self assert: tree isEmpty. - + "Root with two children" tree clear. - tree addAll: #(50 30 70 20 40 60 80). + 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 includes: 60). + self assert: tree root contents equals: 60 ] { #category : 'tests' } diff --git a/src/Containers-BinarySearchTree/CTBSTAbstractNode.class.st b/src/Containers-BinarySearchTree/CTBSTAbstractNode.class.st index 3e8a576..79ea180 100644 --- a/src/Containers-BinarySearchTree/CTBSTAbstractNode.class.st +++ b/src/Containers-BinarySearchTree/CTBSTAbstractNode.class.st @@ -57,12 +57,24 @@ CTBSTAbstractNode >> findMax [ ^ self subclassResponsibility ] +{ #category : 'searching' } +CTBSTAbstractNode >> findMaxNode [ + + ^ self subclassResponsibility +] + { #category : 'searching' } CTBSTAbstractNode >> findMin [ ^ self subclassResponsibility ] +{ #category : 'searching' } +CTBSTAbstractNode >> findMinNode [ + + ^ self subclassResponsibility +] + { #category : 'accessing' } CTBSTAbstractNode >> height [ diff --git a/src/Containers-BinarySearchTree/CTBSTNillNode.class.st b/src/Containers-BinarySearchTree/CTBSTNilNode.class.st similarity index 62% rename from src/Containers-BinarySearchTree/CTBSTNillNode.class.st rename to src/Containers-BinarySearchTree/CTBSTNilNode.class.st index 0fcff28..197293f 100644 --- a/src/Containers-BinarySearchTree/CTBSTNillNode.class.st +++ b/src/Containers-BinarySearchTree/CTBSTNilNode.class.st @@ -6,14 +6,14 @@ I provide default 'do nothing' behavior for all tree operations, eliminating the When elements are added to me, I create and return a new CTBSTNode containing the element, effectively growing the tree. " Class { - #name : 'CTBSTNillNode', + #name : 'CTBSTNilNode', #superclass : 'CTBSTAbstractNode', #category : 'Containers-BinarySearchTree', #package : 'Containers-BinarySearchTree' } { #category : 'adding' } -CTBSTNillNode >> addChild: anObject [ +CTBSTNilNode >> addChild: anObject [ ^ CTBSTNode new contents: anObject; @@ -22,122 +22,134 @@ CTBSTNillNode >> addChild: anObject [ ] { #category : 'accessing' } -CTBSTNillNode >> contents [ +CTBSTNilNode >> contents [ ^ nil ] { #category : 'accessing' } -CTBSTNillNode >> contents: anObject [ +CTBSTNilNode >> contents: anObject [ "Do nothing for nil node" ] { #category : 'enumerating' } -CTBSTNillNode >> elementsFrom: min to: max into: aCollection [ +CTBSTNilNode >> elementsFrom: min to: max into: aCollection [ "Do nothing for nill node" ] { #category : 'enumerating' } -CTBSTNillNode >> elementsGreaterThan: anObject into: aCollection [ +CTBSTNilNode >> elementsGreaterThan: anObject into: aCollection [ "Do nothing for nill node" ] { #category : 'enumerating' } -CTBSTNillNode >> elementsLessThan: anObject into: aCollection [ +CTBSTNilNode >> elementsLessThan: anObject into: aCollection [ "Do nothing for nill node" ] { #category : 'searching' } -CTBSTNillNode >> findMax [ +CTBSTNilNode >> findMax [ ^ nil ] { #category : 'searching' } -CTBSTNillNode >> findMin [ +CTBSTNilNode >> findMaxNode [ + + ^ self +] + +{ #category : 'searching' } +CTBSTNilNode >> findMin [ ^ nil ] +{ #category : 'searching' } +CTBSTNilNode >> findMinNode [ + + ^ self +] + { #category : 'accessing' } -CTBSTNillNode >> height [ +CTBSTNilNode >> height [ ^ 0 ] { #category : 'enumerating' } -CTBSTNillNode >> inOrderDo: aBlock [ +CTBSTNilNode >> inOrderDo: aBlock [ "Do nothing for nil node" ] { #category : 'testing' } -CTBSTNillNode >> isEmpty [ +CTBSTNilNode >> isEmpty [ ^ true ] { #category : 'testing' } -CTBSTNillNode >> isLeaf [ +CTBSTNilNode >> isLeaf [ ^ false ] { #category : 'enumerating' } -CTBSTNillNode >> postOrderDo: aBlock [ +CTBSTNilNode >> postOrderDo: aBlock [ "Do nothing for nill node" ] { #category : 'enumerating' } -CTBSTNillNode >> preOrderDo: aBlock [ +CTBSTNilNode >> preOrderDo: aBlock [ "Do nothing for nill node" ] { #category : 'searching' } -CTBSTNillNode >> predecessorOf: anObject [ +CTBSTNilNode >> predecessorOf: anObject [ ^ nil ] { #category : 'removing' } -CTBSTNillNode >> removeValue: anObject [ +CTBSTNilNode >> removeValue: anObject [ "Element not found - return self unchanged" ^ self ] { #category : 'accessing' } -CTBSTNillNode >> search: anObject [ +CTBSTNilNode >> search: anObject [ ^ nil ] { #category : 'accessing' } -CTBSTNillNode >> size [ +CTBSTNilNode >> size [ ^ 0 ] { #category : 'searching' } -CTBSTNillNode >> successorOf: anObject [ +CTBSTNilNode >> successorOf: anObject [ ^ nil ] { #category : 'testing' } -CTBSTNillNode >> validateBSTProperty [ +CTBSTNilNode >> validateBSTProperty [ ^ true ] { #category : 'private' } -CTBSTNillNode >> validateBSTPropertyWithMin: min max: max [ +CTBSTNilNode >> validateBSTPropertyWithMin: min max: max [ ^ true ] diff --git a/src/Containers-BinarySearchTree/CTBSTNode.class.st b/src/Containers-BinarySearchTree/CTBSTNode.class.st index 03f4964..18158d4 100644 --- a/src/Containers-BinarySearchTree/CTBSTNode.class.st +++ b/src/Containers-BinarySearchTree/CTBSTNode.class.st @@ -22,10 +22,9 @@ Class { CTBSTNode >> addChild: anObject [ anObject < contents - ifTrue: [ left := left addChild: anObject ] - ifFalse: [ - anObject > contents - ifTrue: [ right := right addChild: anObject ] ]. + ifTrue: [ self left: (left addChild: anObject) ] + ifFalse: [ + anObject > contents ifTrue: [ self right: (right addChild: anObject) ] ]. ^ self ] @@ -43,20 +42,13 @@ CTBSTNode >> contents: anObject [ { #category : 'enumerating' } CTBSTNode >> elementsFrom: min to: max into: aCollection [ - "If current node is greater than max, only check left subtree" - contents > max ifTrue: [ - left elementsFrom: min to: max into: aCollection. - ^ self ]. - - "If current node is less than min, only check right subtree" - contents < min ifTrue: [ - right elementsFrom: min to: max into: aCollection. - ^ self ]. - - "Current node is in range, check both subtrees and include current" - left elementsFrom: min to: max into: aCollection. - aCollection add: contents. - right elementsFrom: min to: max into: aCollection + + 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 : 'enumerating' } @@ -81,18 +73,29 @@ CTBSTNode >> elementsLessThan: anObject into: aCollection [ { #category : 'searching' } CTBSTNode >> findMax [ +^ self findMaxNode contents +] + +{ #category : 'searching' } +CTBSTNode >> findMaxNode [ ^ right isEmpty - ifTrue: [ contents ] - ifFalse: [ right findMax ] + ifTrue: [ self ] + ifFalse: [ right findMaxNode ] ] { #category : 'searching' } CTBSTNode >> findMin [ + ^ self findMinNode contents +] + +{ #category : 'searching' } +CTBSTNode >> findMinNode [ + ^ left isEmpty - ifTrue: [ contents ] - ifFalse: [ left findMin ] + ifTrue: [ self ] + ifFalse: [ left findMinNode ] ] { #category : 'accessing' } @@ -113,8 +116,8 @@ CTBSTNode >> inOrderDo: aBlock [ CTBSTNode >> initialize [ super initialize. - left := CTBSTNillNode new parent: self. - right := CTBSTNillNode new parent: self + left := CTBSTNilNode new parent: self. + right := CTBSTNilNode new parent: self ] { #category : 'testing' } @@ -174,38 +177,33 @@ CTBSTNode >> predecessorOf: anObject [ { #category : 'removing' } CTBSTNode >> removeThisNode [ - - "Remove this node and return replacement using standard BST algorithm" - | successorValue | - "Case 1: Leaf node (no children)" - (left isEmpty and: [ right isEmpty ]) - ifTrue: [ ^ CTBSTNillNode new ]. - - "Case 2: Only one child" + "Remove this specific node and return its replacement. + The caller is responsible for linking the replacement into the tree." + | successor | + "Case 1 & 2: Leaf or single child. Return the non-empty child or the nil node." left isEmpty ifTrue: [ ^ right ]. right isEmpty ifTrue: [ ^ left ]. - - "Case 3: Two children - replace with inorder successor" - successorValue := right findMin. - contents := successorValue. - right := right removeValue: successorValue. + + "Case 3: Two children. Replace contents with inorder successor's contents, + then remove the successor node from the right subtree." + successor := right findMinNode. + self contents: successor contents. + self right: (right removeValue: successor contents). ^ self ] { #category : 'removing' } CTBSTNode >> removeValue: anObject [ - - "Remove node with anObject value and return the new subtree root" - + "Find the node with anObject and remove it, returning the new root of this subtree." anObject < contents ifTrue: [ - left := left removeValue: anObject. + self left: (left removeValue: anObject). ^ self ]. - + anObject > contents ifTrue: [ - right := right removeValue: anObject. + self right: (right removeValue: anObject). ^ self ]. - - "Found the node to remove" + + "Found the node to remove. `removeThisNode` will return the replacement." ^ self removeThisNode ] @@ -263,7 +261,8 @@ CTBSTNode >> validateBSTPropertyWithMin: min max: max [ (min notNil and: [ contents < min ]) ifTrue: [ ^ false ]. (max notNil and: [ contents > max ]) ifTrue: [ ^ false ]. - - ^ (left isEmpty or: [ left validateBSTPropertyWithMin: min max: contents ]) - and: [ right isEmpty or: [ right validateBSTPropertyWithMin: contents max: max ] ] + + ^ (left validateBSTPropertyWithMin: min max: contents) and: [ + right validateBSTPropertyWithMin: contents max: max ] + ] diff --git a/src/Containers-BinarySearchTree/CTBinarySearchTree.class.st b/src/Containers-BinarySearchTree/CTBinarySearchTree.class.st index e2a2eff..c723099 100644 --- a/src/Containers-BinarySearchTree/CTBinarySearchTree.class.st +++ b/src/Containers-BinarySearchTree/CTBinarySearchTree.class.st @@ -27,6 +27,7 @@ Class { CTBinarySearchTree >> add: anObject [ root := root addChild: anObject. + root parent: nil. ^ anObject ] @@ -50,25 +51,32 @@ CTBinarySearchTree >> anySatisfy: aBlock [ CTBinarySearchTree >> asArray [ | result | - result := OrderedCollection new. + result := OrderedCollection new: self size. self inOrderDo: [ :each | result add: each ]. ^ result asArray ] +{ #category : 'accessing' } +CTBinarySearchTree >> at: anObject ifAbsent: aBlock [ + + | result | + result := root search: anObject. + ^ result ifNil: [ aBlock value ] ifNotNil: [ result ] +] + { #category : 'removing' } CTBinarySearchTree >> clear [ - root := CTBSTNillNode new + root := CTBSTNilNode new ] { #category : 'enumerating' } CTBinarySearchTree >> collect: aBlock [ | result | - result := OrderedCollection new. + result := OrderedCollection new: self size. self inOrderDo: [ :each | result add: (aBlock value: each) ]. ^ result - ] { #category : 'copying' } @@ -124,20 +132,22 @@ CTBinarySearchTree >> elementsLessThan: anObject [ ^ result ] -{ #category : 'searching' } -CTBinarySearchTree >> findMax [ +{ #category : 'accessing' } +CTBinarySearchTree >> findMax [ - ^ self isEmpty - ifTrue: [ nil ] - ifFalse: [ root findMax ] + ^ root findMax ] -{ #category : 'searching' } +{ #category : 'accessing' } CTBinarySearchTree >> findMin [ - ^ self isEmpty - ifTrue: [ nil ] - ifFalse: [ root findMin ] + ^ root findMin +] + +{ #category : 'accessing' } +CTBinarySearchTree >> first [ + + ^ self findMin ] { #category : 'accessing' } @@ -146,6 +156,22 @@ CTBinarySearchTree >> height [ ^ root isEmpty ifTrue: [ 0 ] ifFalse: [ root height ] ] +{ #category : 'testing' } +CTBinarySearchTree >> ifEmpty: aBlock [ + + ^ self isEmpty + ifTrue: [ aBlock value ] + ifFalse: [ self ] +] + +{ #category : 'testing' } +CTBinarySearchTree >> ifNotEmpty: aBlock [ + + ^ self isEmpty + ifFalse: [ aBlock value: self ] + ifTrue: [ self ] +] + { #category : 'enumerating' } CTBinarySearchTree >> inOrderDo: aBlock [ @@ -155,14 +181,14 @@ CTBinarySearchTree >> inOrderDo: aBlock [ { #category : 'testing' } CTBinarySearchTree >> includes: anObject [ - ^ (root search: anObject) notNil + ^ (self at: anObject ifAbsent: [ nil ]) notNil ] { #category : 'initialization' } CTBinarySearchTree >> initialize [ super initialize. - root := CTBSTNillNode new + self clear ] { #category : 'testing' } @@ -171,6 +197,12 @@ CTBinarySearchTree >> isEmpty [ ^ root isEmpty ] +{ #category : 'accessing' } +CTBinarySearchTree >> last [ + + ^ self findMax +] + { #category : 'enumerating' } CTBinarySearchTree >> postOrderDo: aBlock [ @@ -198,18 +230,17 @@ CTBinarySearchTree >> remove: anObject [ ^ self remove: anObject ifAbsent: [ - self error: 'Element not found: ' , anObject printString ] + NotFound signalFor: anObject in: self ] ] { #category : 'removing' } CTBinarySearchTree >> remove: anObject ifAbsent: aBlock [ - | originalSize | - originalSize := self size. + (self includes: anObject) ifFalse: [ ^ aBlock value ]. + root := root removeValue: anObject. - ^ originalSize = self size - ifTrue: [ aBlock value ] - ifFalse: [ anObject ] + root parent: nil. "The new root has no parent" + ^ anObject ] { #category : 'removing' }