From 279686b7bf33d64ab87d7bdb7781c6efc01b81a0 Mon Sep 17 00:00:00 2001 From: Pankaj Kumar Bind Date: Sat, 29 Mar 2025 15:34:26 +0530 Subject: [PATCH 1/2] Optimize CTQueue for O(1) Remove with Linked List (Fixes #9) --- Pharo64-12.xml | 1 + .../CTQueueTest.class.st | 11 +++ src/Containers-Queue/CTQueue.class.st | 96 ++++++++++++------- src/Containers-Queue/CTQueueNode.class.st | 29 ++++++ 4 files changed, 102 insertions(+), 35 deletions(-) create mode 100644 Pharo64-12.xml create mode 100644 src/Containers-Queue/CTQueueNode.class.st diff --git a/Pharo64-12.xml b/Pharo64-12.xml new file mode 100644 index 0000000..81083e2 --- /dev/null +++ b/Pharo64-12.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Containers-Queue-Tests/CTQueueTest.class.st b/src/Containers-Queue-Tests/CTQueueTest.class.st index d2b909b..fd30955 100644 --- a/src/Containers-Queue-Tests/CTQueueTest.class.st +++ b/src/Containers-Queue-Tests/CTQueueTest.class.st @@ -127,4 +127,15 @@ CTQueueTest >> testRemoveIfNone[ queue := self queueClass new. result := queue removeIfNone: [ 'fallback' ]. self assert: result equals: 'fallback'. +] + +{ #category : #tests } +CTQueueTest >> testLargeQueuePerformance [ + "Verify FIFO behavior and performance with a large queue." + | queue size | + queue := self queueClass new. + size := 1000. + 1 to: size do: [ :i | queue add: i ]. + 1 to: size do: [ :i | self assert: queue remove equals: i ]. + self assert: queue isEmpty. ] \ No newline at end of file diff --git a/src/Containers-Queue/CTQueue.class.st b/src/Containers-Queue/CTQueue.class.st index e3aeefb..2baf9bb 100644 --- a/src/Containers-Queue/CTQueue.class.st +++ b/src/Containers-Queue/CTQueue.class.st @@ -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 ] \ No newline at end of file diff --git a/src/Containers-Queue/CTQueueNode.class.st b/src/Containers-Queue/CTQueueNode.class.st new file mode 100644 index 0000000..430c3b2 --- /dev/null +++ b/src/Containers-Queue/CTQueueNode.class.st @@ -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 +] \ No newline at end of file From 3294c44c7df4a140e3755ce925fa4b5c7e71c172 Mon Sep 17 00:00:00 2001 From: Pankaj Kumar Bind Date: Fri, 18 Apr 2025 14:15:53 +0530 Subject: [PATCH 2/2] more test cases added and that weird file removed also queue size increased --- Pharo64-12.xml | 1 - .../CTQueueTest.class.st | 158 +++++++++++++++--- 2 files changed, 134 insertions(+), 25 deletions(-) delete mode 100644 Pharo64-12.xml diff --git a/Pharo64-12.xml b/Pharo64-12.xml deleted file mode 100644 index 81083e2..0000000 --- a/Pharo64-12.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/Containers-Queue-Tests/CTQueueTest.class.st b/src/Containers-Queue-Tests/CTQueueTest.class.st index fd30955..6d1149e 100644 --- a/src/Containers-Queue-Tests/CTQueueTest.class.st +++ b/src/Containers-Queue-Tests/CTQueueTest.class.st @@ -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, @@ -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." @@ -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. + 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. - self assert: queue remove isNil. + 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 } @@ -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." @@ -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." @@ -121,8 +208,8 @@ 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' ]. @@ -130,12 +217,35 @@ CTQueueTest >> testRemoveIfNone[ ] { #category : #tests } -CTQueueTest >> testLargeQueuePerformance [ - "Verify FIFO behavior and performance with a large queue." - | queue size | +CTQueueTest >> testSizeAccuracy [ + "Test that the size method accurately reflects the number of elements." + | queue | queue := self queueClass new. - size := 1000. - 1 to: size do: [ :i | queue add: i ]. - 1 to: size do: [ :i | self assert: queue remove equals: i ]. + 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. ] \ No newline at end of file