Skip to content

Commit

Permalink
Merge pull request #4 from KaririCode-Framework/develop
Browse files Browse the repository at this point in the history
Add ArrayDeque and ArrayQueue Implementations with Comprehensive Unit…
  • Loading branch information
walmir-silva authored Jun 28, 2024
2 parents d371dae + 3cbbe78 commit 97d93b2
Show file tree
Hide file tree
Showing 4 changed files with 495 additions and 0 deletions.
115 changes: 115 additions & 0 deletions src/Queue/ArrayDeque.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?php

declare(strict_types=1);

namespace KaririCode\DataStructure\Queue;

use KaririCode\Contract\DataStructure\Queue;

/**
* ArrayDeque implementation.
*
* This class implements a double-ended queue using a circular array.
* It provides amortized O(1) time complexity for add and remove operations at both ends.
*
* @category Queues
*
* @implements Queue<mixed>
*/
class ArrayDeque implements Queue
{
private array $elements;
private int $front = 0;
private int $size = 0;
private int $capacity;

public function __construct(int $initialCapacity = 16)
{
$this->capacity = $initialCapacity;
$this->elements = array_fill(0, $this->capacity, null);
}

public function enqueue(mixed $element): void
{
$this->ensureCapacity();
$index = ($this->front + $this->size) % $this->capacity;
$this->elements[$index] = $element;
++$this->size;
}

public function dequeue(): mixed
{
if ($this->isEmpty()) {
return null;
}
$element = $this->elements[$this->front];
$this->elements[$this->front] = null;
$this->front = ($this->front + 1) % $this->capacity;
--$this->size;

return $element;
}

public function peek(): mixed
{
return $this->isEmpty() ? null : $this->elements[$this->front];
}

public function addFirst(mixed $element): void
{
$this->ensureCapacity();
$this->front = ($this->front - 1 + $this->capacity) % $this->capacity;
$this->elements[$this->front] = $element;
++$this->size;
}

public function removeLast(): mixed
{
if ($this->isEmpty()) {
return null;
}
$index = ($this->front + $this->size - 1) % $this->capacity;
$element = $this->elements[$index];
$this->elements[$index] = null;
--$this->size;

return $element;
}

public function peekLast(): mixed
{
if ($this->isEmpty()) {
return null;
}
$index = ($this->front + $this->size - 1) % $this->capacity;

return $this->elements[$index];
}

public function isEmpty(): bool
{
return 0 === $this->size;
}

public function size(): int
{
return $this->size;
}

/**
* Ensures that the deque has enough capacity to add a new element.
*/
private function ensureCapacity(): void
{
if ($this->size === $this->capacity) {
$newCapacity = $this->capacity * 2;
$newElements = array_fill(0, $newCapacity, null);
for ($i = 0; $i < $this->size; ++$i) {
$newElements[$i] = $this->elements[($this->front + $i) % $this->capacity];
}
$this->elements = $newElements;
$this->front = 0;
$this->capacity = $newCapacity;
}
}
}
87 changes: 87 additions & 0 deletions src/Queue/ArrayQueue.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

declare(strict_types=1);

namespace KaririCode\DataStructure\Queue;

use KaririCode\Contract\DataStructure\Queue;

/**
* ArrayQueue implementation.
*
* This class implements a simple queue using a circular array.
* It provides amortized O(1) time complexity for enqueue and dequeue operations.
*
* @category Queues
*
* @author Walmir Silva <walmir.silva@kariricode.org>
* @license MIT
*
* @see https://kariricode.org/
*/
class ArrayQueue implements Queue
{
private array $elements;
private int $front = 0;
private int $size = 0;
private int $capacity;

public function __construct(int $initialCapacity = 16)
{
$this->capacity = $initialCapacity;
$this->elements = array_fill(0, $this->capacity, null);
}

public function enqueue(mixed $element): void
{
$this->ensureCapacity();
$index = ($this->front + $this->size) % $this->capacity;
$this->elements[$index] = $element;
++$this->size;
}

public function dequeue(): mixed
{
if ($this->isEmpty()) {
return null;
}
$element = $this->elements[$this->front];
$this->elements[$this->front] = null;
$this->front = ($this->front + 1) % $this->capacity;
--$this->size;

return $element;
}

public function peek(): mixed
{
return $this->isEmpty() ? null : $this->elements[$this->front];
}

public function isEmpty(): bool
{
return 0 === $this->size;
}

public function size(): int
{
return $this->size;
}

/**
* Ensures that the queue has enough capacity to add a new element.
*/
private function ensureCapacity(): void
{
if ($this->size === $this->capacity) {
$newCapacity = $this->capacity * 2;
$newElements = array_fill(0, $newCapacity, null);
for ($i = 0; $i < $this->size; ++$i) {
$newElements[$i] = $this->elements[($this->front + $i) % $this->capacity];
}
$this->elements = $newElements;
$this->front = 0;
$this->capacity = $newCapacity;
}
}
}
175 changes: 175 additions & 0 deletions tests/Queue/ArrayDequeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
<?php

declare(strict_types=1);

namespace KaririCode\DataStructure\Tests\Queue;

use KaririCode\DataStructure\Queue\ArrayDeque;
use PHPUnit\Framework\TestCase;

final class ArrayDequeTest extends TestCase
{
// Test enqueuing elements
public function testEnqueueAddsElementToEndOfDeque(): void
{
$deque = new ArrayDeque();
$deque->enqueue(1);
$this->assertSame(1, $deque->peek());
}

// Test dequeuing elements
public function testDequeueRemovesElementFromFrontOfDeque(): void
{
$deque = new ArrayDeque();
$deque->enqueue(1);
$deque->enqueue(2);
$this->assertSame(1, $deque->dequeue());
$this->assertSame(2, $deque->peek());
}

// Test dequeuing from an empty deque
public function testDequeueFromEmptyDequeReturnsNull(): void
{
$deque = new ArrayDeque();
$this->assertNull($deque->dequeue());
}

// Test peeking elements
public function testPeekReturnsElementFromFrontWithoutRemovingIt(): void
{
$deque = new ArrayDeque();
$deque->enqueue(1);
$this->assertSame(1, $deque->peek());
$this->assertSame(1, $deque->peek());
}

// Test peeking from an empty deque
public function testPeekFromEmptyDequeReturnsNull(): void
{
$deque = new ArrayDeque();
$this->assertNull($deque->peek());
}

// Test adding elements to the front
public function testAddFirstAddsElementToFrontOfDeque(): void
{
$deque = new ArrayDeque();
$deque->enqueue(1);
$deque->addFirst(2);
$this->assertSame(2, $deque->peek());
}

// Test removing last elements
public function testRemoveLastRemovesElementFromEndOfDeque(): void
{
$deque = new ArrayDeque();
$deque->enqueue(1);
$deque->enqueue(2);
$this->assertSame(2, $deque->removeLast());
$this->assertSame(1, $deque->peekLast());
}

// Test removing last element from an empty deque
public function testRemoveLastFromEmptyDequeReturnsNull(): void
{
$deque = new ArrayDeque();
$this->assertNull($deque->removeLast());
}

// Test peeking last elements
public function testPeekLastReturnsElementFromEndWithoutRemovingIt(): void
{
$deque = new ArrayDeque();
$deque->enqueue(1);
$deque->enqueue(2);
$this->assertSame(2, $deque->peekLast());
$this->assertSame(2, $deque->peekLast());
}

// Test peeking last from an empty deque
public function testPeekLastFromEmptyDequeReturnsNull(): void
{
$deque = new ArrayDeque();
$this->assertNull($deque->peekLast());
}

// Test checking if deque is empty
public function testIsEmptyReturnsTrueIfDequeIsEmpty(): void
{
$deque = new ArrayDeque();
$this->assertTrue($deque->isEmpty());
$deque->enqueue(1);
$this->assertFalse($deque->isEmpty());
}

// Test getting the size of the deque
public function testSizeReturnsNumberOfElementsInDeque(): void
{
$deque = new ArrayDeque();
$this->assertSame(0, $deque->size());
$deque->enqueue(1);
$deque->enqueue(2);
$this->assertSame(2, $deque->size());
}

// Test ensuring capacity of deque
public function testEnsureCapacityDoublesCapacityWhenFull(): void
{
$deque = new ArrayDeque(2);
$deque->enqueue(1);
$deque->enqueue(2);
$deque->enqueue(3); // Should trigger capacity increase
$this->assertSame(3, $deque->size());
}

// Test handling null values in the deque
public function testHandlingNullValuesCorrectly(): void
{
$deque = new ArrayDeque();
$deque->enqueue(null);
$this->assertSame(null, $deque->dequeue());
}

// Test circular nature of the deque
public function testCircularBehavior(): void
{
$deque = new ArrayDeque(3);
$deque->enqueue(1);
$deque->enqueue(2);
$deque->enqueue(3);
$deque->dequeue();
$deque->enqueue(4);
$this->assertSame(2, $deque->dequeue());
$this->assertSame(3, $deque->dequeue());
$this->assertSame(4, $deque->dequeue());
}

// Test deque with various data types
public function testDequeWithVariousDataTypes(): void
{
$deque = new ArrayDeque();
$deque->enqueue(123);
$deque->enqueue('string');
$deque->enqueue([1, 2, 3]);
$deque->enqueue(new \stdClass());

$this->assertSame(123, $deque->dequeue());
$this->assertSame('string', $deque->dequeue());
$this->assertSame([1, 2, 3], $deque->dequeue());
$this->assertInstanceOf(\stdClass::class, $deque->dequeue());
}

// Test deque behavior after mixed operations
public function testDequeBehaviorAfterMixedOperations(): void
{
$deque = new ArrayDeque();
$deque->enqueue(1);
$deque->enqueue(2);
$deque->addFirst(0);
$deque->removeLast();
$deque->enqueue(3);
$this->assertSame(0, $deque->dequeue());
$this->assertSame(1, $deque->dequeue());
$this->assertSame(3, $deque->peekLast());
}
}
Loading

0 comments on commit 97d93b2

Please sign in to comment.