diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000..c9d3368 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,32 @@ +name: CI + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +on: + push: + branches: [master] + pull_request: + branches: [master] + workflow_dispatch: + +jobs: + build: + strategy: + matrix: + os: [macos-latest, ubuntu-latest, windows-latest] + smalltalk: [Pharo64-14, 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/matrix.yml b/.github/workflows/matrix.yml deleted file mode 100644 index 4884745..0000000 --- a/.github/workflows/matrix.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: matrix - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - - # 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: - build: - strategy: - matrix: - os: [ macos-latest, ubuntu-latest ] - smalltalk: [ Pharo64-8.0, Pharo64-7.0 ] - runs-on: ${{ matrix.os }} - name: ${{ matrix.smalltalk }} on ${{ matrix.os }} - steps: - - uses: actions/checkout@v2 - - name: Setup smalltalkCI - uses: hpi-swa/setup-smalltalkCI@v1 - with: - smalltalk-version: ${{ matrix.smalltalk }} - - name: Load Image and Run Tests - run: smalltalkci -s ${{ matrix.smalltalk }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - timeout-minutes: 15 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index be200f0..0000000 --- a/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -language: smalltalk -sudo: false - -# Select operating system(s) -os: - - linux - - osx - -# Select compatible Smalltalk image(s) -smalltalk: - - Pharo-7.0 - - Pharo64-8.0 - - Pharo64-7.0 diff --git a/README.md b/README.md index 5990133..bd18553 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,33 @@ # Containers-Queue +A High-performance, Circular Array based Queue implementation providing efficient FIFO (First In, First Out) operations with dynamic capacity and proper error handling. -[![Build Status](https://travis-ci.com/pharo-containers/Containers-Queue.svg?branch=master)](https://travis-ci.com/pharo-containers/Containers-Queue) -[![License](https://img.shields.io/badge/license-MIT-blue.svg)]() -[![Pharo version](https://img.shields.io/badge/Pharo-7.0-%23aac9ff.svg)](https://pharo.org/download) -[![Pharo version](https://img.shields.io/badge/Pharo-8.0-%23aac9ff.svg)](https://pharo.org/download) +![Pharo Version](https://img.shields.io/badge/Pharo-10+-blue) +[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE) -A queue support FIFO (first in first out) behavior. Now it is a bit limited so feel free to enhance it. +## What is a Queue? -This package is part of the Containers project: This project is to collect, clean, -test and document alternate collection datastructures. Each package is modular so that users -can only load the collection they need without 100 of related collections. +A Queue is a linear data structure that follows the FIFO (First In, First Out) principle. Elements are added at the rear (enqueue) and removed from the front (dequeue). Think of it like a line at a store - the first person in line is the first person served. -## Example +### Key Benefits +- **O(1) Performance**: Constant time enqueue, dequeue, and front operations +- **Dynamic Growth**: Circular array implementation that doubles capacity when needed +- **Memory Safe**: Automatic cleanup prevents memory leaks +- **Simple API**: Clean, intuitive interface following standard queue conventions +- **Robust Error Handling**: Proper empty queue protection with clear error messages -```smalltalk -CTEnvironmentTest >> testDequeue [ - - | queue | - queue := CTQueue new. - queue queue: 1. - queue queue: 2. - queue queue: 3. - self assert: queue dequeue equals: 1. - self assert: queue dequeue equals: 2. -] -``` - -## Loading +## Loading +The following script installs Containers-Queue in Pharo. ```smalltalk Metacello new - baseline: 'ContainersQueue'; - repository: 'github://pharo-containers/Containers-Queue/'; - load. + baseline: 'ContainersQueue'; + repository: 'github://pharo-containers/Containers-Queue/src'; + load. ``` -## If you want to depend on it +## If you want to depend on it + +Add the following code to your Metacello baseline or configuration ```smalltalk spec @@ -43,7 +35,30 @@ spec with: [ spec repository: 'github://pharo-containers/Containers-Queue/src' ]. ``` +## Quick Start + +```smalltalk +"Create a queue with default capacity of 10" +queue := CTQueue new. + +"Enqueue elements" +queue enqueue: 'first'. +queue enqueue: 'second'. +queue enqueue: 'third'. + +"Check front element without removing" +queue front. "Returns 'first'" +queue size. "Returns 3" + +"Dequeue elements (FIFO order)" +queue dequeue. "Returns 'first'" +queue dequeue. "Returns 'second'" +queue dequeue. "Returns 'third'" + +"Queue is now empty" +queue isEmpty. "Returns true" +``` + +## Contributing ----- -The best way to predict the future is to do it! -Less talking more doing. stephane.ducasse@inria.fr +This is part of the Pharo Containers project. Feel free to contribute by implementing additional methods, improving tests, or enhancing documentation. \ No newline at end of file diff --git a/src/BaselineOfContainersQueue/BaselineOfContainersQueue.class.st b/src/BaselineOfContainersQueue/BaselineOfContainersQueue.class.st index 33a4bd4..a2737c1 100644 --- a/src/BaselineOfContainersQueue/BaselineOfContainersQueue.class.st +++ b/src/BaselineOfContainersQueue/BaselineOfContainersQueue.class.st @@ -1,15 +1,19 @@ +" +I represent the baseline for Queue DS Implementation +" Class { - #name : #BaselineOfContainersQueue, - #superclass : #BaselineOf, - #category : #BaselineOfContainersQueue + #name : 'BaselineOfContainersQueue', + #superclass : 'BaselineOf', + #category : 'BaselineOfContainersQueue', + #package : 'BaselineOfContainersQueue' } -{ #category : #baselines } +{ #category : 'baselines' } BaselineOfContainersQueue >> baseline: spec [ spec for: #pharo do: [ spec package: 'Containers-Queue'. - spec package: 'Containers-Queue-Tests' with: [ spec requires: #('Containers-Queue')] + spec package: 'Containers-Queue-Tests' with: [ spec requires: #('Containers-Queue') ] ] ] diff --git a/src/BaselineOfContainersQueue/package.st b/src/BaselineOfContainersQueue/package.st index c54be5f..6076cfe 100644 --- a/src/BaselineOfContainersQueue/package.st +++ b/src/BaselineOfContainersQueue/package.st @@ -1 +1 @@ -Package { #name : #BaselineOfContainersQueue } +Package { #name : 'BaselineOfContainersQueue' } diff --git a/src/Containers-Queue-Tests/CTQueueTest.class.st b/src/Containers-Queue-Tests/CTQueueTest.class.st index 6d1149e..48ef019 100644 --- a/src/Containers-Queue-Tests/CTQueueTest.class.st +++ b/src/Containers-Queue-Tests/CTQueueTest.class.st @@ -1,251 +1,423 @@ " -I'm a simple queue i.e., first in first out structure. -I support basic collection protocol and in addition enqueue and dequeue as in Scala. +I provide a comprehensive suite of unit tests for the CTQueue class. +My tests cover basic operations, FIFO behavior, error handling, state management, +capacity growth, constructors, bulk operations, and various edge cases to ensure +the implementation is robust, correct, and performant. " Class { - #name : #CTQueueTest, - #superclass : #TestCase, - #category : #'Containers-Queue-Tests' + #name : 'CTQueueTest', + #superclass : 'TestCase', + #instVars : [ + 'queue' + ], + #category : 'Containers-Queue-Tests', + #package : 'Containers-Queue-Tests' } -{ #category : #tests } -CTQueueTest >> queueClass [ - ^ CTQueue -] - -{ #category : #tests } -CTQueueTest >> testAdd [ - | queue | - queue := self queueClass new. - queue add: 1. - queue add: 2. - self assert: (queue includes: 1). - self assert: (queue includes: 2) -] - -{ #category : #tests } -CTQueueTest >> testAddAll [ - "Ensure queueAll adds multiple elements at once." - | queue | - queue := self queueClass new. - queue addAll: #(10 20 30 40). - self assert: queue remove equals: 10. - self assert: queue remove equals: 20. - self assert: queue remove equals: 30. - self assert: queue remove equals: 40. -] - -{ #category : #tests } -CTQueueTest >> testAddAllLargeCollection [ - "Test adding a large collection via addAll." - | queue collection size | - queue := self queueClass new. +{ #category : 'running' } +CTQueueTest >> setUp [ + super setUp. + + queue := CTQueue new: 5 +] + +{ #category : 'tests' } +CTQueueTest >> testAlternatingEnqueueDequeue [ + + queue enqueue: 'a'. + self assert: queue dequeue equals: 'a'. + + queue enqueue: 'b'; enqueue: 'c'. + self assert: queue dequeue equals: 'b'. + + queue enqueue: 'd'. + self assert: queue front equals: 'c'. + self assert: queue size equals: 2 +] + +{ #category : 'tests' } +CTQueueTest >> testAsArray [ + + | result | + queue enqueue: 'first'; enqueue: 'second'; enqueue: 'third'. + result := queue asArray. + + self assert: result equals: #('first' 'second' 'third'). + "Elements from front to rear" +] + +{ #category : 'tests' } +CTQueueTest >> testAsOrderedCollection [ + + | result | + queue enqueue: 'a'; enqueue: 'b'; enqueue: 'c'. + result := queue asOrderedCollection. + + self assert: result class equals: OrderedCollection. + self assert: result asArray equals: #('a' 'b' 'c') +] + +{ #category : 'tests' } +CTQueueTest >> testCapacityGrowth [ + + | originalCapacity | + originalCapacity := queue capacity. + + 1 to: originalCapacity do: [ :i | queue enqueue: i ]. + self assert: queue capacity equals: originalCapacity. + + queue enqueue: 'overflow'. + self assert: queue capacity equals: originalCapacity * 2. + self assert: queue size equals: originalCapacity + 1 +] + +{ #category : 'tests' } +CTQueueTest >> testCircularArrayWrapping [ + + | testQueue | + testQueue := CTQueue new: 3. + + "Fill the queue" + testQueue enqueue: 1; enqueue: 2; enqueue: 3. + + "Remove front elements" + testQueue dequeue; dequeue. + + "Add more elements to test wraparound" + testQueue enqueue: 4; enqueue: 5. + + self assert: testQueue front equals: 3. + self assert: testQueue size equals: 3 +] + +{ #category : 'tests' } +CTQueueTest >> testCopyCreatesNewObject [ + + | copy | + queue + enqueue: 'a'; + enqueue: 'b'. + copy := queue copy. + self deny: queue identicalTo: copy +] + +{ #category : 'tests' } +CTQueueTest >> testCopyHasSameContents [ + + | copy | + queue + enqueue: 'first'; + enqueue: 'second'; + enqueue: 'third'. + copy := queue copy. + self assert: copy size equals: queue size. + self assert: copy front equals: queue front +] + +{ #category : 'tests' } +CTQueueTest >> testCopyIsIndependent [ + + | copy | + queue enqueue: 'a'; enqueue: 'b'. + copy := queue copy. + "Now, modify the original queue" + queue dequeue. + queue enqueue: 'c'. + + self assert: copy size equals: 2. + self assert: copy front equals: 'a'. + self deny: copy front equals: queue front +] + +{ #category : 'tests' } +CTQueueTest >> testDefaultQueueCreation [ + + | defaultQueue | + defaultQueue := CTQueue new. + self assert: defaultQueue capacity equals: 10. + self assert: defaultQueue isEmpty. + self assert: defaultQueue size equals: 0 +] + +{ #category : 'tests' } +CTQueueTest >> testDequeueAllElements [ + + queue + enqueue: 'a'; + enqueue: 'b'; + enqueue: 'c'. + queue + dequeue; + dequeue; + dequeue. + self assert: queue isEmpty. + self assert: queue size equals: 0 +] + +{ #category : 'tests' } +CTQueueTest >> testDequeueMultipleElements [ + + queue enqueue: 'first'; enqueue: 'second'; enqueue: 'third'. + + self assert: queue dequeue equals: 'first'. + self assert: queue dequeue equals: 'second'. + self assert: queue size equals: 1. + self assert: queue front equals: 'third' +] + +{ #category : 'tests' } +CTQueueTest >> testDequeueSingleElement [ + + | element | + queue enqueue: 'test'. + element := queue dequeue. + self assert: element equals: 'test'. + self assert: queue isEmpty. + self assert: queue size equals: 0 +] + +{ #category : 'tests' } +CTQueueTest >> testDoIteration [ + + | elements | + queue enqueue: 'first'; enqueue: 'second'; enqueue: 'third'. + elements := OrderedCollection new. + + queue do: [ :each | elements add: each ]. + + self assert: elements asArray equals: #('first' 'second' 'third'). + self assert: queue size equals: 3. "Iteration should not modify the queue" +] + +{ #category : 'tests' } +CTQueueTest >> testDynamicQueue [ + + | testQueue | + testQueue := CTQueue new: 2. + testQueue + enqueue: 'a'; + enqueue: 'b'. + self assert: testQueue capacity equals: 2. + "Enqueue one more - should double capacity" + testQueue enqueue: 'c'. + self assert: testQueue capacity equals: 4. + self assert: testQueue size equals: 3. + self assert: testQueue front equals: 'a' +] + +{ #category : 'tests' } +CTQueueTest >> testEnqueueAll [ + + | result | + result := queue enqueueAll: #('a' 'b' 'c'). + + self assert: queue size equals: 3. + self assert: queue front equals: 'a'. + self assert: result identicalTo: queue +] + +{ #category : 'tests' } +CTQueueTest >> testEnqueueAllLargeCollection [ + + | collection size | size := 50000. collection := (1 to: size) asArray. - queue addAll: collection. + queue enqueueAll: collection. self assert: queue size equals: size. - 1 to: size do: [ :i | self assert: queue remove equals: i ]. - self assert: queue isEmpty. + 1 to: size do: [ :i | self assert: queue dequeue equals: i ]. + self assert: queue isEmpty ] -{ #category : #tests } -CTQueueTest >> testAddGarantyFIFOOrder [ - "Ensure elements are dequeued in FIFO order." - | queue | - queue := self queueClass new. - queue add: 'first'. - queue add: 'second'. - queue add: 'third'. - self assert: queue remove equals: 'first'. - self assert: queue remove equals: 'second'. - self assert: queue remove equals: 'third'. +{ #category : 'tests' } +CTQueueTest >> testEnqueueAllWithEmptyCollection [ + + | result | + result := queue enqueueAll: #(). self assert: queue isEmpty. + self assert: result identicalTo: queue +] + +{ #category : 'tests' } +CTQueueTest >> testEnqueueMultipleElements [ + + queue + enqueue: 'first'; + enqueue: 'second'; + enqueue: 'third'. + self assert: queue size equals: 3. + self assert: queue front equals: 'first'. + self deny: queue isEmpty ] -{ #category : #tests } -CTQueueTest >> testAddNilElement [ - "Test adding a nil element to the queue." - | queue | - queue := self queueClass new. - queue add: nil. +{ #category : 'tests' } +CTQueueTest >> testEnqueueNilElement [ + + queue enqueue: nil. self assert: queue size equals: 1. - self assert: queue peek isNil. - self assert: queue remove isNil. - self assert: queue isEmpty. + self assert: queue front isNil. + self assert: queue dequeue isNil. + self assert: queue isEmpty ] -{ #category : #tests } -CTQueueTest >> testDoIteration [ - "Test iterating over the queue with do: in FIFO order." - | queue collected | - queue := self queueClass new. - queue addAll: #(10 20 30 40). - collected := OrderedCollection new. - queue do: [ :each | collected add: each ]. - self assert: collected asArray equals: #(10 20 30 40). - self assert: queue size equals: 4. "Iteration should not modify the queue" +{ #category : 'tests' } +CTQueueTest >> testEnqueueSingleElement [ + + queue enqueue: 'first'. + self assert: queue size equals: 1. + self deny: queue isEmpty. + self assert: queue front equals: 'first' +] + +{ #category : 'tests' } +CTQueueTest >> testErrorHandling [ + + self should: [ queue dequeue ] raise: Error. + self should: [ queue front ] raise: Error. + + queue enqueue: 'test'. + queue dequeue. + + self should: [ queue dequeue ] raise: Error. + self should: [ queue front ] raise: Error ] -{ #category : #tests } -CTQueueTest >> testEmptyQueue [ - self assert: self queueClass new isEmpty +{ #category : 'tests' } +CTQueueTest >> testFIFOOrdering [ + + queue enqueue: 'first'; enqueue: 'second'; enqueue: 'third'; enqueue: 'fourth'. + + self assert: queue dequeue equals: 'first'. + self assert: queue dequeue equals: 'second'. + self assert: queue dequeue equals: 'third'. + self assert: queue dequeue equals: 'fourth' ] -{ #category : #tests } -CTQueueTest >> testEmptyQueueRemove [ - | queue | - queue := self queueClass new. - self assert: queue remove isNil. +{ #category : 'tests' } +CTQueueTest >> testFrontWithoutRemoving [ + + queue enqueue: 'first'; enqueue: 'second'. + + self assert: queue front equals: 'first'. + self assert: queue size equals: 2. + self assert: queue front equals: 'first'. "Still there" ] -{ #category : #tests } +{ #category : 'tests' } CTQueueTest >> testIncludes [ - "Test the includes: method for existing and non-existing elements." - | queue | - queue := self queueClass new. - queue addAll: #(5 10 15). + + queue enqueueAll: #(5 10 15). self assert: (queue includes: 10). self deny: (queue includes: 20). - self assert: queue size equals: 3. "includes: should not modify the queue" -] - -{ #category : #tests } -CTQueueTest >> testInterleavedAddRemove [ - "Test interleaved add and remove operations to ensure FIFO order and correctness." - | queue | - queue := self queueClass new. - queue add: 1. - queue add: 2. - self assert: queue remove equals: 1. - queue add: 3. - self assert: queue remove equals: 2. - queue add: 4. - self assert: queue remove equals: 3. - self assert: queue remove equals: 4. - self assert: queue isEmpty. + self assert: queue size equals: 3. +] + +{ #category : 'tests' } +CTQueueTest >> testInterleavedEnqueueDequeue [ + + queue enqueue: 1. + queue enqueue: 2. + self assert: queue dequeue equals: 1. + queue enqueue: 3. + self assert: queue dequeue equals: 2. + queue enqueue: 4. + self assert: queue dequeue equals: 3. + self assert: queue dequeue equals: 4. + self assert: queue isEmpty ] -{ #category : #tests } +{ #category : 'tests' } CTQueueTest >> testIsEmpty [ - "Ensure isEmpty works correctly." - | queue | - queue := self queueClass new. + self assert: queue isEmpty. - queue add: 1. + queue enqueue: 'test'. self deny: queue isEmpty. - queue remove. + queue dequeue. + self assert: queue isEmpty +] + +{ #category : 'tests' } +CTQueueTest >> testQueueCreationWithCapacity [ + + | testQueue | + testQueue := CTQueue new: 3. + self assert: testQueue capacity equals: 3. + self assert: testQueue isEmpty. + self assert: testQueue size equals: 0 +] + +{ #category : 'tests' } +CTQueueTest >> testQueueCreationWithInvalidCapacity [ + + self should: [ CTQueue new: -1 ] raise: Error. + self should: [ CTQueue new: -10 ] raise: Error +] + +{ #category : 'tests' } +CTQueueTest >> testQueueCreationWithZeroCapacity [ + + | zeroQueue | + zeroQueue := CTQueue new: 0. + + self assert: zeroQueue capacity equals: 0. + self assert: zeroQueue isEmpty. + self assert: zeroQueue size equals: 0. + + zeroQueue enqueue: 'first'. + self assert: zeroQueue capacity equals: 10. + self assert: zeroQueue size equals: 1 +] + +{ #category : 'tests' } +CTQueueTest >> testRemoveAll [ + + queue + enqueue: 'a'; + enqueue: 'b'; + enqueue: 'c'. + queue removeAll. self assert: queue isEmpty. + self assert: queue size equals: 0. + queue enqueue: 'd'. + self deny: queue isEmpty. + self assert: queue size equals: 1. + self assert: queue front equals: 'd' ] -{ #category : #tests } -CTQueueTest >> testLargeQueuePerformance [ - "Verify FIFO behavior and performance with a large queue." - | queue size startTime endTime duration maxDuration | - queue := self queueClass new. - size := 100000. - - "Measure time to add elements" - startTime := DateAndTime now. - 1 to: size do: [ :i | queue add: i ]. - endTime := DateAndTime now. - duration := endTime - startTime. - maxDuration := 5 seconds. "Expect adding 100,000 elements to take less than 5 seconds" - self assert: duration < maxDuration description: 'Adding elements took too long'. - - "Measure time to remove elements and verify FIFO order" - startTime := DateAndTime now. - 1 to: size do: [ :i | self assert: queue remove equals: i ]. - endTime := DateAndTime now. - duration := endTime - startTime. - self assert: duration < maxDuration description: 'Removing elements took too long'. +{ #category : 'tests' } +CTQueueTest >> testRemoveAllOnEmptyQueue [ + queue removeAll. + self assert: queue isEmpty. + self assert: queue size equals: 0 +] + +{ #category : 'tests' } +CTQueueTest >> testSearchExistingElement [ + + queue enqueue: 'a'; enqueue: 'b'; enqueue: 'c'; enqueue: 'b'. + + self assert: (queue search: 'a') equals: 1. "Front element" + self assert: (queue search: 'b') equals: 2. "First occurrence" + self assert: (queue search: 'c') equals: 3 ] -{ #category : #tests } -CTQueueTest >> testPeek [ - "Ensure peek returns the first element without removing it." - | queue | - queue := self queueClass new. - queue add: 42. - queue add: 99. - self assert: queue peek equals: 42. - self assert: queue size equals: 2. "Peek should not remove elements" -] - -{ #category : #tests } -CTQueueTest >> testPoll [ - "Ensure poll behaves correctly, returning nil when empty." - | queue | - queue := self queueClass new. - queue add: 'A'. - queue add: 'B'. - self assert: queue poll equals: 'A'. - self assert: queue poll equals: 'B'. - self assert: queue poll isNil. -] - -{ #category : #tests } -CTQueueTest >> testQueue [ - self assert: self queueClass new isEmpty -] - -{ #category : #tests } -CTQueueTest >> testQueueGarantyFIFOOrder [ - self assert: self queueClass new isEmpty -] - -{ #category : #tests } -CTQueueTest >> testRemove [ - "Ensure remove behaves correctly, returning nil when empty." - | queue | - queue := self queueClass new. - queue add: 1. - queue add: 2. - queue add: 3. - self assert: queue remove equals: 1. - self assert: queue remove equals: 2 -] - -{ #category : #tests } -CTQueueTest >> testRemoveIfNone [ - "Ensure removeIfNone works correctly." - | queue result | - queue := self queueClass new. - result := queue removeIfNone: [ 'fallback' ]. - self assert: result equals: 'fallback'. -] - -{ #category : #tests } -CTQueueTest >> testSizeAccuracy [ - "Test that the size method accurately reflects the number of elements." - | queue | - queue := self queueClass new. +{ #category : 'tests' } +CTQueueTest >> testSearchNonExistentElement [ + + queue enqueue: 'a'; enqueue: 'b'; enqueue: 'c'. + + self assert: (queue search: 'x') equals: -1 +] + +{ #category : 'tests' } +CTQueueTest >> testSize [ + self assert: queue size equals: 0. - queue add: 1. + queue enqueue: 'a'. self assert: queue size equals: 1. - queue addAll: #(2 3 4). - self assert: queue size equals: 4. - queue remove. + queue enqueue: 'b'; enqueue: 'c'. self assert: queue size equals: 3. - queue removeIfNone: [ nil ]. - self assert: queue size equals: 2. + queue dequeue. + self assert: queue size equals: 2 ] - -{ #category : #tests } -CTQueueTest >> testStressAddRemove [ - "Stress test with many add and remove operations." - | queue iterations | - queue := self queueClass new. - iterations := 10000. - 1 to: iterations do: [ :i | - queue add: i. - self assert: queue size equals: 1. - self assert: queue remove equals: i. - self assert: queue isEmpty. - ]. - "Add multiple elements and remove them" - queue addAll: (1 to: 1000). - 1 to: 1000 do: [ :i | self assert: queue remove equals: i ]. - self assert: queue isEmpty. -] \ No newline at end of file diff --git a/src/Containers-Queue-Tests/package.st b/src/Containers-Queue-Tests/package.st index ccf49f0..c9e25b5 100644 --- a/src/Containers-Queue-Tests/package.st +++ b/src/Containers-Queue-Tests/package.st @@ -1 +1 @@ -Package { #name : #'Containers-Queue-Tests' } +Package { #name : 'Containers-Queue-Tests' } diff --git a/src/Containers-Queue/CTQueue.class.st b/src/Containers-Queue/CTQueue.class.st index 2baf9bb..1d4eb2e 100644 --- a/src/Containers-Queue/CTQueue.class.st +++ b/src/Containers-Queue/CTQueue.class.st @@ -1,111 +1,219 @@ " -I'm a simple FIFO queue i.e., first in first out structure. I support basic collection protocol with efficient O(1) add and remove operations using a singly linked list. +I implement a simple Queue (FIFO - First In, First Out) that grows dynamically as needed. +- #enqueue: adds a new object to the rear of the queue. +- #dequeue returns and removes the front element from the queue. +- #front answers the front element of the queue without removing it. " Class { - #name : #CTQueue, - #superclass : #Object, + #name : 'CTQueue', + #superclass : 'Object', #instVars : [ - 'head', - 'tail', + 'elements', + 'frontIndex', + 'rearIndex', 'size' ], - #category : #'Containers-Queue' + #category : 'Containers-Queue', + #package : 'Containers-Queue' } -{ #category : #adding } -CTQueue >> add: anElement [ - "Add an element to the end of the queue (FIFO order)." - | newNode | - newNode := CTQueueNode new value: anElement. - head ifNil: [ - head := newNode. - tail := newNode. - ] ifNotNil: [ - tail next: newNode. - tail := newNode. +{ #category : 'instance creation' } +CTQueue class >> new [ + + "Create a new queue with default capacity of 10" + + ^ self new: 10 +] + +{ #category : 'instance creation' } +CTQueue class >> new: anInteger [ + + anInteger < 0 ifTrue: [ self error: 'Initial capacity cannot be negative' ]. + ^ self basicNew + initializeWithCapacity: anInteger; + yourself +] + +{ #category : 'converting' } +CTQueue >> asArray [ + + | result | + self isEmpty ifTrue: [ ^ #() ]. + + result := Array new: self size. + 1 to: self size do: [ :i | + result at: i put: (elements at: (self indexAt: i - 1)) ]. - size := size + 1. - ^ anElement + ^ result ] -{ #category : #adding } -CTQueue >> addAll: aCollection [ - "Add all elements from aCollection to the end of the queue." - aCollection do: [ :each | self add: each ]. - ^ aCollection +{ #category : 'converting' } +CTQueue >> asOrderedCollection [ + + | result | + result := OrderedCollection new: self size. + self do: [ :each | result add: each ]. + ^ result ] -{ #category : #iterating } +{ #category : 'accessing' } +CTQueue >> capacity [ + + ^ elements size +] + +{ #category : 'copying' } +CTQueue >> copy [ + + | newQueue | + newQueue := self class new: self capacity. + self do: [ :each | newQueue enqueue: each ]. + ^ newQueue +] + +{ #category : 'removing' } +CTQueue >> dequeue [ + + | element | + self isEmpty ifTrue: [ self error: 'Queue is empty' ]. + + element := elements at: frontIndex. + elements at: frontIndex put: nil. + frontIndex := self nextIndex: frontIndex. + size := size - 1. + + ^ element +] + +{ #category : 'enumerating' } CTQueue >> do: aBlock [ - "Iterate over elements in FIFO order." - | current | - current := head. - [ current notNil ] whileTrue: [ - aBlock value: current value. - current := current next. + + self isEmpty ifTrue: [ ^ self ]. + + 0 to: size - 1 do: [ :offset | + aBlock value: (elements at: (self indexAt: offset)) ] ] -{ #category : #testing } +{ #category : 'adding' } +CTQueue >> enqueue: anObject [ + + self isFull ifTrue: [ self grow ]. + + elements at: rearIndex put: anObject. + rearIndex := self nextIndex: rearIndex. + size := size + 1. + ^ self +] + +{ #category : 'adding' } +CTQueue >> enqueueAll: aCollection [ + + aCollection do: [ :each | self enqueue: each ]. + ^ self +] + +{ #category : 'accessing' } +CTQueue >> front [ + + self isEmpty ifTrue: [ self error: 'Queue is empty' ]. + ^ elements at: frontIndex +] + +{ #category : 'private' } +CTQueue >> grow [ + + | newElements newCapacity | + newCapacity := (self capacity = 0 ifTrue: [ 10 ] ifFalse: [ self capacity * 2 ]). + newElements := Array new: newCapacity. + + 1 to: size do: [ :i | + newElements at: i put: (elements at: (self indexAt: i - 1)) + ]. + + elements := newElements. + frontIndex := 1. + rearIndex := size + 1 +] + +{ #category : 'testing' } CTQueue >> includes: anElement [ - "Check if anElement exists in the queue." - | current | - current := head. - [ current notNil ] whileTrue: [ - current value = anElement ifTrue: [ ^ true ]. - current := current next. + + self isEmpty ifTrue: [ ^ false ]. + + 0 to: size - 1 do: [ :offset | + (elements at: (self indexAt: offset)) = anElement ifTrue: [ ^ true ] ]. ^ false ] -{ #category : #initialization } -CTQueue >> initialize [ - "Initialize an empty queue." - super initialize. - head := nil. - tail := nil. - size := 0. +{ #category : 'accessing' } +CTQueue >> indexAt: offset [ + + ^ ((frontIndex - 1 + offset) rem: self capacity) + 1 +] + +{ #category : 'initialization' } +CTQueue >> initializeWithCapacity: anInteger [ + + elements := Array new: anInteger. + frontIndex := 1. + rearIndex := 1. + size := 0 ] -{ #category : #testing } +{ #category : 'testing' } CTQueue >> isEmpty [ - "Return true if the queue is empty." - ^ head isNil + + ^ size = 0 ] -{ #category : #accessing } -CTQueue >> peek [ - "Return the front element without removing it, or nil if empty." - ^ head ifNil: [ nil ] ifNotNil: [ head value ] +{ #category : 'testing' } +CTQueue >> isFull [ + + ^ size = self capacity ] -{ #category : #removing } -CTQueue >> poll [ - "Return and remove the front element, or nil if empty." - ^ self remove +{ #category : 'private' } +CTQueue >> nextIndex: currentIndex [ + + ^ currentIndex = self capacity + ifTrue: [ 1 ] + ifFalse: [ currentIndex + 1 ] ] -{ #category : #removing } -CTQueue >> remove [ - "Return and remove the oldest element, or nil if empty." - | value | - head ifNil: [ ^ nil ]. - value := head value. - head := head next. - head ifNil: [ tail := nil ]. - size := size - 1. - ^ value +{ #category : 'accessing' } +CTQueue >> peek [ + + "Answer the front element, or nil if empty." + self isEmpty ifTrue: [ ^ nil ]. + ^ self front ] -{ #category : #removing } -CTQueue >> removeIfNone: aBlock [ - "Return and remove the oldest element, or evaluate aBlock if empty." - head ifNil: [ ^ aBlock value ]. - ^ self remove +{ #category : 'removing' } +CTQueue >> removeAll [ + + elements := Array new: self capacity. + frontIndex := 1. + rearIndex := 1. + size := 0 ] -{ #category : #accessing } +{ #category : 'accessing' } +CTQueue >> search: anObject [ + + self isEmpty ifTrue: [ ^ -1 ]. + + 0 to: size - 1 do: [ :offset | + (elements at: (self indexAt: offset)) = anObject ifTrue: [ + ^ offset + 1 + ] + ]. + ^ -1 +] + +{ #category : 'accessing' } CTQueue >> size [ - "Return the number of elements in the queue." + ^ size -] \ No newline at end of file +] diff --git a/src/Containers-Queue/CTQueueNode.class.st b/src/Containers-Queue/CTQueueNode.class.st deleted file mode 100644 index 430c3b2..0000000 --- a/src/Containers-Queue/CTQueueNode.class.st +++ /dev/null @@ -1,29 +0,0 @@ -Class { - #name : #CTQueueNode, - #superclass : #Object, - #instVars : [ - 'value', - 'next' - ], - #category : #'Containers-Queue' -} - -{ #category : #accessing } -CTQueueNode >> next [ - ^ next -] - -{ #category : #accessing } -CTQueueNode >> next: aNode [ - next := aNode -] - -{ #category : #accessing } -CTQueueNode >> value [ - ^ value -] - -{ #category : #accessing } -CTQueueNode >> value: anObject [ - value := anObject -] \ No newline at end of file diff --git a/src/Containers-Queue/package.st b/src/Containers-Queue/package.st index 500d4dd..bee4046 100644 --- a/src/Containers-Queue/package.st +++ b/src/Containers-Queue/package.st @@ -1 +1 @@ -Package { #name : #'Containers-Queue' } +Package { #name : 'Containers-Queue' }