Skip to content

Optimize CTQueue for O(1) Remove with Linked List (Fixes #9) #11

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 139 additions & 18 deletions src/Containers-Queue-Tests/CTQueueTest.class.st
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
"
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.


"
Class {
#name : #CTQueueTest,
Expand All @@ -25,6 +23,31 @@ CTQueueTest >> testAdd [
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.
size := 50000.
collection := (1 to: size) asArray.
queue addAll: collection.
self assert: queue size equals: size.
1 to: size do: [ :i | self assert: queue remove equals: i ].
self assert: queue isEmpty.
]

{ #category : #tests }
CTQueueTest >> testAddGarantyFIFOOrder [
"Ensure elements are dequeued in FIFO order."
Expand All @@ -40,37 +63,66 @@ CTQueueTest >> testAddGarantyFIFOOrder [
]

{ #category : #tests }
CTQueueTest >> testEmptyQueue [
self assert: self queueClass new isEmpty
CTQueueTest >> testAddNilElement [
"Test adding a nil element to the queue."
| queue |
queue := self queueClass new.
queue add: nil.
self assert: queue size equals: 1.
self assert: queue peek isNil.
self assert: queue remove isNil.
self assert: queue isEmpty.
]

{ #category : #tests }
CTQueueTest >> testQueue [
self assert: self queueClass new isEmpty
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 >> testQueueGarantyFIFOOrder [
CTQueueTest >> testEmptyQueue [
self assert: self queueClass new isEmpty
]

{ #category : #tests }
CTQueueTest >> testAddAll [
"Ensure queueAll adds multiple elements at once."
CTQueueTest >> testEmptyQueueRemove [
| 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.
self assert: queue remove isNil.
]

{ #category : #tests }
CTQueueTest >> testEmptyQueueRemove [
CTQueueTest >> testIncludes [
"Test the includes: method for existing and non-existing elements."
| queue |
queue := self queueClass new.
self assert: queue remove isNil.
queue addAll: #(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.
]

{ #category : #tests }
Expand All @@ -85,6 +137,31 @@ CTQueueTest >> testIsEmpty [
self assert: queue isEmpty.
]

{ #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'.

self assert: queue isEmpty.
]

{ #category : #tests }
CTQueueTest >> testPeek [
"Ensure peek returns the first element without removing it."
Expand All @@ -108,6 +185,16 @@ CTQueueTest >> testPoll [
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."
Expand All @@ -121,10 +208,44 @@ CTQueueTest >> testRemove [
]

{ #category : #tests }
CTQueueTest >> testRemoveIfNone[
"Ensure dequeueIfNone works correctly."
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.
self assert: queue size equals: 0.
queue add: 1.
self assert: queue size equals: 1.
queue addAll: #(2 3 4).
self assert: queue size equals: 4.
queue remove.
self assert: queue size equals: 3.
queue removeIfNone: [ nil ].
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.
]
96 changes: 61 additions & 35 deletions src/Containers-Queue/CTQueue.class.st
Original file line number Diff line number Diff line change
@@ -1,85 +1,111 @@
"
I'm a simple FIFO queue i.e., first in first out structure. I support basic collection protocol and in addition enqueue and dequeue as in Scala.
My basic support of collection API should be reviewd and probably improved (should check atomic queue protocol).


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.
"
Class {
#name : #CTQueue,
#superclass : #Object,
#instVars : [
'elements'
'head',
'tail',
'size'
],
#category : #'Containers-Queue'
}

{ #category : #adding }
CTQueue >> add: anElement [
"Add an element to the receiver. Note that the addition makes sure that when iterating over the receiver added first element are accessed first."

elements addLast: 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.
].
size := size + 1.
^ anElement
]

{ #category : #adding }
CTQueue >> addAll: aCollection [
"Add the elements contained in the argument to the receiver. Note that the addition makes sure that when iterating over the receiver added first element are accessed first."

elements addAllLast: aCollection.
"Add all elements from aCollection to the end of the queue."
aCollection do: [ :each | self add: each ].
^ aCollection
]

{ #category : #iterating }
CTQueue >> do: aBlock [
"iterates the elements of the receiver starting first by first added elements."

elements do: aBlock
"Iterate over elements in FIFO order."
| current |
current := head.
[ current notNil ] whileTrue: [
aBlock value: current value.
current := current next.
]
]

{ #category : #testing }
CTQueue >> includes: anElement [

^ elements includes: anElement
"Check if anElement exists in the queue."
| current |
current := head.
[ current notNil ] whileTrue: [
current value = anElement ifTrue: [ ^ true ].
current := current next.
].
^ false
]

{ #category : #initialization }
CTQueue >> initialize [
"Initialize an empty queue."
super initialize.
elements := OrderedCollection new.
head := nil.
tail := nil.
size := 0.
]

{ #category : #testing }
CTQueue >> isEmpty [

^ elements isEmpty
"Return true if the queue is empty."
^ head isNil
]

{ #category : #removing }
CTQueue >> remove [
"Return the older element of the receiver.."

^ elements ifEmpty: [ nil ] ifNotEmpty: [ elements removeFirst ].
{ #category : #accessing }
CTQueue >> peek [
"Return the front element without removing it, or nil if empty."
^ head ifNil: [ nil ] ifNotNil: [ head value ]
]

{ #category : #removing }
CTQueue >> removeIfNone: aBlock [
"Return the older element of the receiver.."
elements ifEmpty: [ ^ aBlock value ].
^ elements removeFirst
CTQueue >> poll [
"Return and remove the front element, or nil if empty."
^ self remove
]

{ #category : #removing }
CTQueue >> poll [
"Returns and removes the front element, or nil if empty."
^ elements ifEmpty: [ nil ] ifNotEmpty: [ elements removeFirst ].
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 [
^ elements ifEmpty: [ nil ] ifNotEmpty: [ elements first ].
{ #category : #removing }
CTQueue >> removeIfNone: aBlock [
"Return and remove the oldest element, or evaluate aBlock if empty."
head ifNil: [ ^ aBlock value ].
^ self remove
]

{ #category : #accessing }
CTQueue >> size [
^ elements size
"Return the number of elements in the queue."
^ size
]
29 changes: 29 additions & 0 deletions src/Containers-Queue/CTQueueNode.class.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
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
]
Loading