Skip to content

Add ArrayDeque and ArrayQueue Implementations with Comprehensive Unit… #4

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 1 commit into from
Jun 28, 2024
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
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
Loading