diff --git a/README.md b/README.md index 39d1b21..a0d39d0 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Containers-Stack -A High-performance, Array based Stack implementation providing efficient LIFO (Last In, First Out) operations with fixed capacity and proper bounds checking. +A High-performance, Array-based Stack implementation providing efficient LIFO (Last In, First Out) operations with dynamic growth and proper bounds checking. ![Pharo Version](https://img.shields.io/badge/Pharo-10+-blue) [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE) @@ -10,10 +10,9 @@ A Stack is a linear data structure that follows the LIFO (Last In, First Out) pr ### Key Benefits - **O(1) Performance**: Constant time push, pop, and top operations -- **Fixed Memory Usage**: Array-based implementation with bounded capacity -- **Memory Safe**: Automatic cleanup prevents memory leaks +- **Dynamic Growth**: Automatically expands capacity when needed - no size limits - **Simple API**: Clean, intuitive interface following standard conventions -- **Robust Error Handling**: Proper stack overflow and underflow protection +- **Robust Error Handling**: Proper stack underflow protection ## Loading The following script installs Containers-Stack in Pharo. @@ -38,21 +37,19 @@ spec ## Quick Start ```smalltalk -"Create a stack with capacity of 5" -stack := CTStack new: 5. +"Create a stack (grows automatically when needed)" +stack := CTStack new: 2. +stack capacity. "Returns 2" -"Push elements" -stack push: 'first'. -stack push: 'second'. -stack push: 'third'. - -"Check top element without removing" -stack top. "Returns 'third'" +"Push elements - grows beyond initial capacity" +stack push: 'first'; push: 'second'; push: 'third'. +stack capacity. "Returns 4 (doubled automatically)" stack size. "Returns 3" -"Pop elements (LIFO order)" +"LIFO operations" +stack top. "Returns 'third'" stack pop. "Returns 'third'" -stack pop. "Returns 'second'" +stack pop. "Returns 'second'" stack pop. "Returns 'first'" "Stack is now empty" diff --git a/src/Containers-Stack-Tests/CTStackTest.class.st b/src/Containers-Stack-Tests/CTStackTest.class.st index cae87d0..5bbaae1 100644 --- a/src/Containers-Stack-Tests/CTStackTest.class.st +++ b/src/Containers-Stack-Tests/CTStackTest.class.st @@ -50,18 +50,6 @@ CTStackTest >> testAsOrderedCollection [ self assert: result asArray equals: #('c' 'b' 'a'). ] -{ #category : 'tests' } -CTStackTest >> testAvailableSpace [ - - self assert: stack availableSpace equals: 5. - stack push: 'a'. - self assert: stack availableSpace equals: 4. - stack push: 'b'; push: 'c'. - self assert: stack availableSpace equals: 2. - stack pop. - self assert: stack availableSpace equals: 3 -] - { #category : 'tests' } CTStackTest >> testCapacity [ @@ -70,6 +58,20 @@ CTStackTest >> testCapacity [ self assert: stack capacity equals: 5. "Capacity doesn't change" ] +{ #category : 'tests' } +CTStackTest >> testCapacityGrowth [ + + | originalCapacity | + originalCapacity := stack capacity. + + 1 to: originalCapacity do: [ :i | stack push: i ]. + self assert: stack capacity equals: originalCapacity. + + stack push: 'overflow'. + self assert: stack capacity equals: originalCapacity * 2. + self assert: stack size equals: originalCapacity + 1 +] + { #category : 'tests' } CTStackTest >> testCopyCreatesNewObject [ @@ -85,12 +87,30 @@ CTStackTest >> testCopyCreatesNewObject [ CTStackTest >> testCopyHasSameContents [ | copy | - stack push: 'first'; push: 'second'; push: 'third'. + stack + push: 'first'; + push: 'second'; + push: 'third'. copy := stack copy. - + self assert: copy size equals: stack size. - self assert: copy top equals: stack top. - self assert: copy capacity equals: stack capacity + self assert: copy top equals: stack top +] + +{ #category : 'tests' } +CTStackTest >> testCopyIsIndependent [ + + | copy | + stack push: 'a'; push: 'b'. + copy := stack copy. + + "Now, modify the original stack" + stack pop. + stack push: 'c'. + + self assert: copy size equals: 2. + self assert: copy top equals: 'b'. + self deny: copy top equals: stack top. ] { #category : 'tests' } @@ -98,11 +118,10 @@ CTStackTest >> testDefaultStackCreation [ | defaultStack | defaultStack := CTStack new. - + self assert: defaultStack capacity equals: 10. self assert: defaultStack isEmpty. - self assert: defaultStack size equals: 0. - self assert: defaultStack availableSpace equals: 10 + self assert: defaultStack size equals: 0 ] { #category : 'tests' } @@ -118,23 +137,44 @@ CTStackTest >> testDoIteration [ ] { #category : 'tests' } -CTStackTest >> testIsEmpty [ +CTStackTest >> testDynamicStack [ - self assert: stack isEmpty. + | testStack | + testStack := CTStack new: 2. + + testStack + push: 'a'; + push: 'b'. + self assert: testStack capacity equals: 2. + + "Push one more - should double capacity" + testStack push: 'c'. + self assert: testStack capacity equals: 4. + self assert: testStack size equals: 3. + self assert: testStack top equals: 'c' +] + +{ #category : 'tests' } +CTStackTest >> testErrorHandling [ + + self should: [ stack pop ] raise: Error. + self should: [ stack top ] raise: Error. + stack push: 'test'. - self deny: stack isEmpty. stack pop. - self assert: stack isEmpty + + self should: [ stack pop ] raise: Error. + self should: [ stack top ] raise: Error ] { #category : 'tests' } -CTStackTest >> testIsFull [ +CTStackTest >> testIsEmpty [ - self deny: stack isFull. - stack push: 'a'; push: 'b'; push: 'c'; push: 'd'; push: 'e'. - self assert: stack isFull. + self assert: stack isEmpty. + stack push: 'test'. + self deny: stack isEmpty. stack pop. - self deny: stack isFull + self assert: stack isEmpty ] { #category : 'tests' } @@ -167,13 +207,18 @@ CTStackTest >> testLargeCapacityStack [ { #category : 'tests' } CTStackTest >> testPopAllElements [ - stack push: 'a'; push: 'b'; push: 'c'. - - stack pop; pop; pop. - + stack + push: 'a'; + push: 'b'; + push: 'c'. + + stack + pop; + pop; + pop. + self assert: stack isEmpty. - self assert: stack size equals: 0. - self assert: stack availableSpace equals: 5 + self assert: stack size equals: 0 ] { #category : 'tests' } @@ -190,33 +235,24 @@ CTStackTest >> testPopMultipleElements [ { #category : 'tests' } CTStackTest >> testPopSingleElement [ - | element | + | element | stack push: 'test'. element := stack pop. - + self assert: element equals: 'test'. self assert: stack isEmpty. - self assert: stack size equals: 0. - self assert: stack availableSpace equals: 5 -] - -{ #category : 'tests' } -CTStackTest >> testPushAllOverflow [ - - stack push: 'existing'. - - self should: [ stack pushAll: #('a' 'b' 'c' 'd' 'e') ] raise: Error + self assert: stack size equals: 0 ] { #category : 'tests' } -CTStackTest >> testPushAllWithArray [ +CTStackTest >> testPushAll [ - | result | + | result | result := stack pushAll: #('a' 'b' 'c'). self assert: stack size equals: 3. - self assert: result equals: 'c'. - self assert: stack top equals: 'c' + self assert: stack top equals: 'c'. + self assert: result identicalTo: stack ] { #category : 'tests' } @@ -224,76 +260,51 @@ CTStackTest >> testPushAllWithEmptyCollection [ | result | result := stack pushAll: #(). - - self assert: stack size equals: 0. - self assert: result isNil. - self assert: stack isEmpty -] -{ #category : 'tests' } -CTStackTest >> testPushAllWithOrderedCollection [ - - | collection result | - collection := OrderedCollection with: 'x' with: 'y' with: 'z'. - result := stack pushAll: collection. - - self assert: stack size equals: 3. - self assert: result equals: 'z'. - self assert: stack top equals: 'z' + self assert: stack isEmpty. + self assert: result identicalTo: stack. ] { #category : 'tests' } CTStackTest >> testPushMultipleElements [ - stack push: 'first'; push: 'second'; push: 'third'. - + stack + push: 'first'; + push: 'second'; + push: 'third'. + self assert: stack size equals: 3. - self assert: stack top equals: 'third'. - self assert: stack availableSpace equals: 2. - self deny: stack isEmpty. - self deny: stack isFull + self assert: stack top equals: 'third'. + self deny: stack isEmpty ] { #category : 'tests' } CTStackTest >> testPushSingleElement [ stack push: 'first'. - + self assert: stack size equals: 1. self deny: stack isEmpty. self deny: stack isFull. - self assert: stack top equals: 'first'. - self assert: stack availableSpace equals: 4 -] - -{ #category : 'tests' } -CTStackTest >> testPushToCapacity [ - - stack push: 'a'; push: 'b'; push: 'c'; push: 'd'; push: 'e'. - - self assert: stack size equals: 5. - self assert: stack isFull. - self assert: stack availableSpace equals: 0. - self assert: stack top equals: 'e' + self assert: stack top equals: 'first' ] { #category : 'tests' } -CTStackTest >> testPushToFullStack [ - - stack push: 'a'; push: 'b'; push: 'c'; push: 'd'; push: 'e'. - - self should: [ stack push: 'overflow' ] raise: Error -] - -{ #category : 'removing' } CTStackTest >> testRemoveAll [ - stack push: 'a'; push: 'b'; push: 'c'. + stack + push: 'a'; + push: 'b'; + push: 'c'. stack removeAll. - + self assert: stack isEmpty. self assert: stack size equals: 0. - self assert: stack availableSpace equals: 5 + + stack push: 'd'. + self deny: stack isEmpty. + self assert: stack size equals: 1. + self assert: stack top equals: 'd'. ] { #category : 'tests' } @@ -337,24 +348,36 @@ CTStackTest >> testSize [ { #category : 'tests' } CTStackTest >> testStackCreationWithCapacity [ - - | testStack | +| testStack | testStack := CTStack new: 3. - + self assert: testStack capacity equals: 3. self assert: testStack isEmpty. - self assert: testStack size equals: 0. - self assert: testStack availableSpace equals: 3 + self assert: testStack size equals: 0 ] { #category : 'tests' } CTStackTest >> testStackCreationWithInvalidCapacity [ - self should: [ CTStack new: 0 ] raise: Error. self should: [ CTStack new: -1 ] raise: Error. self should: [ CTStack new: -10 ] raise: Error ] +{ #category : 'tests' } +CTStackTest >> testStackCreationWithZeroCapacity [ + + | zeroStack | + zeroStack := CTStack new: 0. + + self assert: zeroStack capacity equals: 0. + self assert: zeroStack isEmpty. + self assert: zeroStack size equals: 0. + + zeroStack push: 'first'. + self assert: zeroStack capacity equals: 10. + self assert: zeroStack size equals: 1 +] + { #category : 'tests' } CTStackTest >> testTopWithoutRemoving [ diff --git a/src/Containers-Stack/CTStack.class.st b/src/Containers-Stack/CTStack.class.st index 79669ca..98b8f89 100644 --- a/src/Containers-Stack/CTStack.class.st +++ b/src/Containers-Stack/CTStack.class.st @@ -1,8 +1,7 @@ " -I implement a simple Stack. - -- #push: adds a new object of any kind on top of the stack. -- #pop returns the first element and remove it from the stack. +I implement a simple Stack that grows dynamically as needed. +- #push: adds a new object of any kind on top of the stack. +- #pop returns the first element and remove it from the stack. - #top answer the first element of the stack without removing it. " Class { @@ -10,8 +9,7 @@ Class { #superclass : 'Object', #instVars : [ 'elements', - 'topIndex', - 'capacity' + 'topIndex' ], #category : 'Containers-Stack', #package : 'Containers-Stack' @@ -28,10 +26,10 @@ CTStack class >> new [ { #category : 'instance creation' } CTStack class >> new: anInteger [ - anInteger < 1 ifTrue: [ self error: 'Capacity must be positive' ]. + anInteger < 0 ifTrue: [ self error: 'Initial capacity cannot be negative' ]. ^ self basicNew - initializeWithCapacity: anInteger; - yourself + initializeWithCapacity: anInteger; + yourself ] { #category : 'converting' } @@ -56,30 +54,22 @@ CTStack >> asOrderedCollection [ ^ result ] -{ #category : 'accessing' } -CTStack >> availableSpace [ - - ^ capacity - self size -] - { #category : 'accessing' } CTStack >> capacity [ - ^ capacity + ^ elements size ] { #category : 'copying' } CTStack >> copy [ - | copy | - copy := self class new: capacity. - self isEmpty ifTrue: [ ^ copy ]. - + | newStack | + newStack := self class new: self capacity. 1 to: topIndex do: [ :i | - copy push: (elements at: i) + newStack push: (elements at: i) ]. - ^ copy + ^ newStack ] { #category : 'enumerating' } @@ -92,11 +82,28 @@ CTStack >> do: aBlock [ ] ] +{ #category : 'private' } +CTStack >> grow [ + + | newElements newCapacity | + + "Double the size. If the current size is 0, start with a default size." + newCapacity := (self capacity = 0 ifTrue: [ 10 ] ifFalse: [ self capacity * 2 ]). + newElements := Array new: newCapacity. + + newElements + replaceFrom: 1 + to: self size + with: elements + startingAt: 1. + + elements := newElements +] + { #category : 'initialization' } CTStack >> initializeWithCapacity: anInteger [ - capacity := anInteger. - elements := Array new: capacity. + elements := Array new: anInteger. topIndex := 0 ] @@ -108,12 +115,11 @@ CTStack >> isEmpty [ ^ topIndex = 0 ] -{ #category : 'testing' } +{ #category : 'private' } CTStack >> isFull [ + "Return true if stack is at maximum capacity" - "Return true if stack is at maximum capacity" - - ^ topIndex = capacity + ^ topIndex = self capacity ] { #category : 'removing' } @@ -131,18 +137,18 @@ CTStack >> pop [ { #category : 'adding' } CTStack >> push: anObject [ - self isFull ifTrue: [ self error: 'Stack is full' ]. - + self isFull ifTrue: [ self grow ]. + topIndex := topIndex + 1. elements at: topIndex put: anObject. - ^ anObject + ^ self ] { #category : 'showing' } CTStack >> pushAll: aCollection [ - aCollection do: [ :each | self push: each ]. - ^ aCollection isEmpty ifFalse: [ aCollection last ] ifTrue: [ nil ] +aCollection do: [ :each | self push: each ]. + ^ self ] { #category : 'removing' }