Skip to content

Commit 97d93b2

Browse files
authored
Merge pull request #4 from KaririCode-Framework/develop
Add ArrayDeque and ArrayQueue Implementations with Comprehensive Unit…
2 parents d371dae + 3cbbe78 commit 97d93b2

File tree

4 files changed

+495
-0
lines changed

4 files changed

+495
-0
lines changed

src/Queue/ArrayDeque.php

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace KaririCode\DataStructure\Queue;
6+
7+
use KaririCode\Contract\DataStructure\Queue;
8+
9+
/**
10+
* ArrayDeque implementation.
11+
*
12+
* This class implements a double-ended queue using a circular array.
13+
* It provides amortized O(1) time complexity for add and remove operations at both ends.
14+
*
15+
* @category Queues
16+
*
17+
* @implements Queue<mixed>
18+
*/
19+
class ArrayDeque implements Queue
20+
{
21+
private array $elements;
22+
private int $front = 0;
23+
private int $size = 0;
24+
private int $capacity;
25+
26+
public function __construct(int $initialCapacity = 16)
27+
{
28+
$this->capacity = $initialCapacity;
29+
$this->elements = array_fill(0, $this->capacity, null);
30+
}
31+
32+
public function enqueue(mixed $element): void
33+
{
34+
$this->ensureCapacity();
35+
$index = ($this->front + $this->size) % $this->capacity;
36+
$this->elements[$index] = $element;
37+
++$this->size;
38+
}
39+
40+
public function dequeue(): mixed
41+
{
42+
if ($this->isEmpty()) {
43+
return null;
44+
}
45+
$element = $this->elements[$this->front];
46+
$this->elements[$this->front] = null;
47+
$this->front = ($this->front + 1) % $this->capacity;
48+
--$this->size;
49+
50+
return $element;
51+
}
52+
53+
public function peek(): mixed
54+
{
55+
return $this->isEmpty() ? null : $this->elements[$this->front];
56+
}
57+
58+
public function addFirst(mixed $element): void
59+
{
60+
$this->ensureCapacity();
61+
$this->front = ($this->front - 1 + $this->capacity) % $this->capacity;
62+
$this->elements[$this->front] = $element;
63+
++$this->size;
64+
}
65+
66+
public function removeLast(): mixed
67+
{
68+
if ($this->isEmpty()) {
69+
return null;
70+
}
71+
$index = ($this->front + $this->size - 1) % $this->capacity;
72+
$element = $this->elements[$index];
73+
$this->elements[$index] = null;
74+
--$this->size;
75+
76+
return $element;
77+
}
78+
79+
public function peekLast(): mixed
80+
{
81+
if ($this->isEmpty()) {
82+
return null;
83+
}
84+
$index = ($this->front + $this->size - 1) % $this->capacity;
85+
86+
return $this->elements[$index];
87+
}
88+
89+
public function isEmpty(): bool
90+
{
91+
return 0 === $this->size;
92+
}
93+
94+
public function size(): int
95+
{
96+
return $this->size;
97+
}
98+
99+
/**
100+
* Ensures that the deque has enough capacity to add a new element.
101+
*/
102+
private function ensureCapacity(): void
103+
{
104+
if ($this->size === $this->capacity) {
105+
$newCapacity = $this->capacity * 2;
106+
$newElements = array_fill(0, $newCapacity, null);
107+
for ($i = 0; $i < $this->size; ++$i) {
108+
$newElements[$i] = $this->elements[($this->front + $i) % $this->capacity];
109+
}
110+
$this->elements = $newElements;
111+
$this->front = 0;
112+
$this->capacity = $newCapacity;
113+
}
114+
}
115+
}

src/Queue/ArrayQueue.php

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace KaririCode\DataStructure\Queue;
6+
7+
use KaririCode\Contract\DataStructure\Queue;
8+
9+
/**
10+
* ArrayQueue implementation.
11+
*
12+
* This class implements a simple queue using a circular array.
13+
* It provides amortized O(1) time complexity for enqueue and dequeue operations.
14+
*
15+
* @category Queues
16+
*
17+
* @author Walmir Silva <walmir.silva@kariricode.org>
18+
* @license MIT
19+
*
20+
* @see https://kariricode.org/
21+
*/
22+
class ArrayQueue implements Queue
23+
{
24+
private array $elements;
25+
private int $front = 0;
26+
private int $size = 0;
27+
private int $capacity;
28+
29+
public function __construct(int $initialCapacity = 16)
30+
{
31+
$this->capacity = $initialCapacity;
32+
$this->elements = array_fill(0, $this->capacity, null);
33+
}
34+
35+
public function enqueue(mixed $element): void
36+
{
37+
$this->ensureCapacity();
38+
$index = ($this->front + $this->size) % $this->capacity;
39+
$this->elements[$index] = $element;
40+
++$this->size;
41+
}
42+
43+
public function dequeue(): mixed
44+
{
45+
if ($this->isEmpty()) {
46+
return null;
47+
}
48+
$element = $this->elements[$this->front];
49+
$this->elements[$this->front] = null;
50+
$this->front = ($this->front + 1) % $this->capacity;
51+
--$this->size;
52+
53+
return $element;
54+
}
55+
56+
public function peek(): mixed
57+
{
58+
return $this->isEmpty() ? null : $this->elements[$this->front];
59+
}
60+
61+
public function isEmpty(): bool
62+
{
63+
return 0 === $this->size;
64+
}
65+
66+
public function size(): int
67+
{
68+
return $this->size;
69+
}
70+
71+
/**
72+
* Ensures that the queue has enough capacity to add a new element.
73+
*/
74+
private function ensureCapacity(): void
75+
{
76+
if ($this->size === $this->capacity) {
77+
$newCapacity = $this->capacity * 2;
78+
$newElements = array_fill(0, $newCapacity, null);
79+
for ($i = 0; $i < $this->size; ++$i) {
80+
$newElements[$i] = $this->elements[($this->front + $i) % $this->capacity];
81+
}
82+
$this->elements = $newElements;
83+
$this->front = 0;
84+
$this->capacity = $newCapacity;
85+
}
86+
}
87+
}

tests/Queue/ArrayDequeTest.php

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace KaririCode\DataStructure\Tests\Queue;
6+
7+
use KaririCode\DataStructure\Queue\ArrayDeque;
8+
use PHPUnit\Framework\TestCase;
9+
10+
final class ArrayDequeTest extends TestCase
11+
{
12+
// Test enqueuing elements
13+
public function testEnqueueAddsElementToEndOfDeque(): void
14+
{
15+
$deque = new ArrayDeque();
16+
$deque->enqueue(1);
17+
$this->assertSame(1, $deque->peek());
18+
}
19+
20+
// Test dequeuing elements
21+
public function testDequeueRemovesElementFromFrontOfDeque(): void
22+
{
23+
$deque = new ArrayDeque();
24+
$deque->enqueue(1);
25+
$deque->enqueue(2);
26+
$this->assertSame(1, $deque->dequeue());
27+
$this->assertSame(2, $deque->peek());
28+
}
29+
30+
// Test dequeuing from an empty deque
31+
public function testDequeueFromEmptyDequeReturnsNull(): void
32+
{
33+
$deque = new ArrayDeque();
34+
$this->assertNull($deque->dequeue());
35+
}
36+
37+
// Test peeking elements
38+
public function testPeekReturnsElementFromFrontWithoutRemovingIt(): void
39+
{
40+
$deque = new ArrayDeque();
41+
$deque->enqueue(1);
42+
$this->assertSame(1, $deque->peek());
43+
$this->assertSame(1, $deque->peek());
44+
}
45+
46+
// Test peeking from an empty deque
47+
public function testPeekFromEmptyDequeReturnsNull(): void
48+
{
49+
$deque = new ArrayDeque();
50+
$this->assertNull($deque->peek());
51+
}
52+
53+
// Test adding elements to the front
54+
public function testAddFirstAddsElementToFrontOfDeque(): void
55+
{
56+
$deque = new ArrayDeque();
57+
$deque->enqueue(1);
58+
$deque->addFirst(2);
59+
$this->assertSame(2, $deque->peek());
60+
}
61+
62+
// Test removing last elements
63+
public function testRemoveLastRemovesElementFromEndOfDeque(): void
64+
{
65+
$deque = new ArrayDeque();
66+
$deque->enqueue(1);
67+
$deque->enqueue(2);
68+
$this->assertSame(2, $deque->removeLast());
69+
$this->assertSame(1, $deque->peekLast());
70+
}
71+
72+
// Test removing last element from an empty deque
73+
public function testRemoveLastFromEmptyDequeReturnsNull(): void
74+
{
75+
$deque = new ArrayDeque();
76+
$this->assertNull($deque->removeLast());
77+
}
78+
79+
// Test peeking last elements
80+
public function testPeekLastReturnsElementFromEndWithoutRemovingIt(): void
81+
{
82+
$deque = new ArrayDeque();
83+
$deque->enqueue(1);
84+
$deque->enqueue(2);
85+
$this->assertSame(2, $deque->peekLast());
86+
$this->assertSame(2, $deque->peekLast());
87+
}
88+
89+
// Test peeking last from an empty deque
90+
public function testPeekLastFromEmptyDequeReturnsNull(): void
91+
{
92+
$deque = new ArrayDeque();
93+
$this->assertNull($deque->peekLast());
94+
}
95+
96+
// Test checking if deque is empty
97+
public function testIsEmptyReturnsTrueIfDequeIsEmpty(): void
98+
{
99+
$deque = new ArrayDeque();
100+
$this->assertTrue($deque->isEmpty());
101+
$deque->enqueue(1);
102+
$this->assertFalse($deque->isEmpty());
103+
}
104+
105+
// Test getting the size of the deque
106+
public function testSizeReturnsNumberOfElementsInDeque(): void
107+
{
108+
$deque = new ArrayDeque();
109+
$this->assertSame(0, $deque->size());
110+
$deque->enqueue(1);
111+
$deque->enqueue(2);
112+
$this->assertSame(2, $deque->size());
113+
}
114+
115+
// Test ensuring capacity of deque
116+
public function testEnsureCapacityDoublesCapacityWhenFull(): void
117+
{
118+
$deque = new ArrayDeque(2);
119+
$deque->enqueue(1);
120+
$deque->enqueue(2);
121+
$deque->enqueue(3); // Should trigger capacity increase
122+
$this->assertSame(3, $deque->size());
123+
}
124+
125+
// Test handling null values in the deque
126+
public function testHandlingNullValuesCorrectly(): void
127+
{
128+
$deque = new ArrayDeque();
129+
$deque->enqueue(null);
130+
$this->assertSame(null, $deque->dequeue());
131+
}
132+
133+
// Test circular nature of the deque
134+
public function testCircularBehavior(): void
135+
{
136+
$deque = new ArrayDeque(3);
137+
$deque->enqueue(1);
138+
$deque->enqueue(2);
139+
$deque->enqueue(3);
140+
$deque->dequeue();
141+
$deque->enqueue(4);
142+
$this->assertSame(2, $deque->dequeue());
143+
$this->assertSame(3, $deque->dequeue());
144+
$this->assertSame(4, $deque->dequeue());
145+
}
146+
147+
// Test deque with various data types
148+
public function testDequeWithVariousDataTypes(): void
149+
{
150+
$deque = new ArrayDeque();
151+
$deque->enqueue(123);
152+
$deque->enqueue('string');
153+
$deque->enqueue([1, 2, 3]);
154+
$deque->enqueue(new \stdClass());
155+
156+
$this->assertSame(123, $deque->dequeue());
157+
$this->assertSame('string', $deque->dequeue());
158+
$this->assertSame([1, 2, 3], $deque->dequeue());
159+
$this->assertInstanceOf(\stdClass::class, $deque->dequeue());
160+
}
161+
162+
// Test deque behavior after mixed operations
163+
public function testDequeBehaviorAfterMixedOperations(): void
164+
{
165+
$deque = new ArrayDeque();
166+
$deque->enqueue(1);
167+
$deque->enqueue(2);
168+
$deque->addFirst(0);
169+
$deque->removeLast();
170+
$deque->enqueue(3);
171+
$this->assertSame(0, $deque->dequeue());
172+
$this->assertSame(1, $deque->dequeue());
173+
$this->assertSame(3, $deque->peekLast());
174+
}
175+
}

0 commit comments

Comments
 (0)