diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f935c41..9b88a04a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,3 +44,4 @@ * **BC** - `Psl\Collection\IndexAccessInterface::at()` now throw `Psl\Collection\Exception\OutOfBoundsException` instead of `Psl\Exception\InvariantViolationException` if `$k` is out-of-bounds. * **BC** - `Psl\Collection\AccessibleCollectionInterface::slice` signature has changed from `slice(int $start, int $length): static` to `slice(int $start, ?int $length = null): static` * **BC** - All psl functions previously accepting `callable`, now accept only `Closure`. +* **BC** - `Psl\DataStructure\QueueInterface::dequeue`, and `Psl\DataStructure\StackInterface::pop` now throw `Psl\DataStructure\Exception\UnderflowException` instead of `Psl\Exception\InvariantViolationException` when the data structure is empty. diff --git a/docs/component/data-structure.md b/docs/component/data-structure.md index ac688a5a..c96849d7 100644 --- a/docs/component/data-structure.md +++ b/docs/component/data-structure.md @@ -13,13 +13,13 @@ #### `Interfaces` - [PriorityQueueInterface](./../../src/Psl/DataStructure/PriorityQueueInterface.php#L12) -- [QueueInterface](./../../src/Psl/DataStructure/QueueInterface.php#L17) -- [StackInterface](./../../src/Psl/DataStructure/StackInterface.php#L17) +- [QueueInterface](./../../src/Psl/DataStructure/QueueInterface.php#L16) +- [StackInterface](./../../src/Psl/DataStructure/StackInterface.php#L16) #### `Classes` -- [PriorityQueue](./../../src/Psl/DataStructure/PriorityQueue.php#L20) -- [Queue](./../../src/Psl/DataStructure/Queue.php#L21) -- [Stack](./../../src/Psl/DataStructure/Stack.php#L19) +- [PriorityQueue](./../../src/Psl/DataStructure/PriorityQueue.php#L18) +- [Queue](./../../src/Psl/DataStructure/Queue.php#L17) +- [Stack](./../../src/Psl/DataStructure/Stack.php#L17) diff --git a/src/Psl/DataStructure/Exception/ExceptionInterface.php b/src/Psl/DataStructure/Exception/ExceptionInterface.php new file mode 100644 index 00000000..e1ae86fb --- /dev/null +++ b/src/Psl/DataStructure/Exception/ExceptionInterface.php @@ -0,0 +1,11 @@ +> + * @var array> */ private array $queue = []; @@ -49,7 +47,7 @@ public function peek(): mixed return null; } - $keys = Vec\keys($this->queue); + $keys = array_keys($this->queue); // Retrieve the highest priority. $priority = Math\max($keys) ?? 0; @@ -58,7 +56,7 @@ public function peek(): mixed $nodes = $this->queue[$priority] ?? []; // Retrieve the first node of the list. - return Iter\first($nodes); + return $nodes[0] ?? null; } /** @@ -69,63 +67,50 @@ public function peek(): mixed */ public function pull(): mixed { - if (0 === $this->count()) { + try { + return $this->dequeue(); + } catch (Exception\UnderflowException) { return null; } - - /** @psalm-suppress MissingThrowsDocblock - the queue is not empty */ - return $this->dequeue(); } /** * Dequeues a node from the queue. * - * @throws Psl\Exception\InvariantViolationException If the Queue is invalid. + * @throws Exception\UnderflowException If the queue is empty. * * @return T */ public function dequeue(): mixed { - Psl\invariant(0 !== $this->count(), 'Cannot dequeue a node from an empty Queue.'); + if (0 === $this->count()) { + throw new Exception\UnderflowException('Cannot dequeue a node from an empty queue.'); + } /** - * Peeking into a non-empty queue always results in a value. + * retrieve the highest priority. * - * @var T $node + * @var int */ - $node = $this->peek(); - - $this->drop(); - - return $node; - } - - private function drop(): void - { + $priority = Math\max(array_keys($this->queue)); /** - * Retrieve the highest priority. - * - * @var int $priority + * retrieve the list of nodes with the priority `$priority`. */ - $priority = Math\max(Vec\keys($this->queue)); - + $nodes = $this->queue[$priority]; /** - * Retrieve the list of nodes with the priority `$priority`. + * shift the first node out. */ - $nodes = $this->queue[$priority]; - - // If the list contained only this node, - // remove the list of nodes with priority `$priority`. - if (1 === Iter\count($nodes)) { + $node = array_shift($nodes); + /** + * If the list contained only this node, remove the list of nodes with priority `$priority`. + */ + if ([] === $nodes) { unset($this->queue[$priority]); } else { - /** - * otherwise, drop the first node. - * - * @psalm-suppress MissingThrowsDocblock - */ - $this->queue[$priority] = Vec\values(Dict\drop($nodes, 1)); + $this->queue[$priority] = $nodes; } + + return $node; } /** @@ -136,7 +121,7 @@ private function drop(): void public function count(): int { $count = 0; - foreach ($this->queue as $_priority => $list) { + foreach ($this->queue as $list) { $count += count($list); } diff --git a/src/Psl/DataStructure/Queue.php b/src/Psl/DataStructure/Queue.php index 5154b11b..01ee6839 100644 --- a/src/Psl/DataStructure/Queue.php +++ b/src/Psl/DataStructure/Queue.php @@ -4,11 +4,7 @@ namespace Psl\DataStructure; -use Psl; -use Psl\Dict; -use Psl\Iter; -use Psl\Vec; - +use function array_shift; use function count; /** @@ -43,7 +39,7 @@ public function enqueue(mixed $node): void */ public function peek(): mixed { - return Iter\first($this->queue); + return $this->queue[0] ?? null; } /** @@ -54,29 +50,24 @@ public function peek(): mixed */ public function pull(): mixed { - if (0 === $this->count()) { - return null; - } - - /** @psalm-suppress MissingThrowsDocblock - we are sure that the queue is not empty. */ - return $this->dequeue(); + return array_shift($this->queue); } /** * Dequeues a node from the queue. * - * @throws Psl\Exception\InvariantViolationException If the Queue is invalid. + * @throws Exception\UnderflowException If the queue is empty. * * @return T */ public function dequeue(): mixed { - Psl\invariant(0 !== $this->count(), 'Cannot dequeue a node from an empty Queue.'); - - $node = $this->queue[0]; - $this->queue = Vec\values(Dict\drop($this->queue, 1)); + if ([] === $this->queue) { + throw new Exception\UnderflowException('Cannot dequeue a node from an empty queue.'); + } - return $node; + /** @var T */ + return array_shift($this->queue); } /** diff --git a/src/Psl/DataStructure/QueueInterface.php b/src/Psl/DataStructure/QueueInterface.php index ed02d0ea..3ba790c0 100644 --- a/src/Psl/DataStructure/QueueInterface.php +++ b/src/Psl/DataStructure/QueueInterface.php @@ -5,7 +5,6 @@ namespace Psl\DataStructure; use Countable; -use Psl; /** * An interface representing a queue data structure ( FIFO ). @@ -42,7 +41,7 @@ public function pull(): mixed; /** * Retrieves and removes the node at the head of this queue. * - * @throws Psl\Exception\InvariantViolationException If the Queue is invalid. + * @throws Exception\UnderflowException If the queue is empty. * * @return T */ diff --git a/src/Psl/DataStructure/Stack.php b/src/Psl/DataStructure/Stack.php index 9c2559c3..ca15e9fc 100644 --- a/src/Psl/DataStructure/Stack.php +++ b/src/Psl/DataStructure/Stack.php @@ -4,10 +4,8 @@ namespace Psl\DataStructure; -use Psl; -use Psl\Dict; -use Psl\Iter; -use Psl\Vec; +use function array_pop; +use function count; /** * An basic implementation of a stack data structure ( LIFO ). @@ -41,7 +39,9 @@ public function push(mixed $item): void */ public function peek(): mixed { - return Iter\last($this->items); + $items = $this->items; + + return array_pop($items); } /** @@ -52,38 +52,33 @@ public function peek(): mixed */ public function pull(): mixed { - if (0 === $this->count()) { - return null; - } - - /** @psalm-suppress MissingThrowsDocblock - the stack is not empty. */ - return $this->pop(); + return array_pop($this->items); } /** * Retrieve and removes the most recently added item that was not yet removed. * - * @throws Psl\Exception\InvariantViolationException If the stack is empty. + * @throws Exception\UnderflowException If the stack is empty. * * @return T */ public function pop(): mixed { - Psl\invariant(0 !== ($i = $this->count()), 'Cannot pop an item from an empty Stack.'); - - /** @var int<0, max> $position */ - $position = $i - 1; - $tail = $this->items[$position]; - $this->items = Vec\values(Dict\take($this->items, $position)); + if ([] === $this->items) { + throw new Exception\UnderflowException('Cannot pop an item from an empty stack.'); + } - return $tail; + /** @var T */ + return array_pop($this->items); } /** * Count the items in the stack. + * + * @return int<0, max> */ public function count(): int { - return Iter\count($this->items); + return count($this->items); } } diff --git a/src/Psl/DataStructure/StackInterface.php b/src/Psl/DataStructure/StackInterface.php index 8c368d2b..47e769d5 100644 --- a/src/Psl/DataStructure/StackInterface.php +++ b/src/Psl/DataStructure/StackInterface.php @@ -5,7 +5,6 @@ namespace Psl\DataStructure; use Countable; -use Psl; /** * An interface representing a stack data structure ( LIFO ). @@ -42,7 +41,7 @@ public function pull(): mixed; /** * Retrieve and removes the most recently added item that was not yet removed. * - * @throws Psl\Exception\InvariantViolationException If the stack is empty. + * @throws Exception\UnderflowException If the stack is empty. * * @return T */ @@ -50,6 +49,8 @@ public function pop(): mixed; /** * Count the items in the stack. + * + * @return int<0, max> */ public function count(): int; } diff --git a/src/Psl/Exception/UnderflowException.php b/src/Psl/Exception/UnderflowException.php new file mode 100644 index 00000000..f98680b3 --- /dev/null +++ b/src/Psl/Exception/UnderflowException.php @@ -0,0 +1,11 @@ +expectException(Psl\Exception\InvariantViolationException::class); - $this->expectExceptionMessage('Cannot dequeue a node from an empty Queue.'); + $this->expectException(DataStructure\Exception\UnderflowException::class); + $this->expectExceptionMessage('Cannot dequeue a node from an empty queue.'); $queue->dequeue(); } diff --git a/tests/unit/DataStructure/QueueTest.php b/tests/unit/DataStructure/QueueTest.php index 8d6673e7..31954029 100644 --- a/tests/unit/DataStructure/QueueTest.php +++ b/tests/unit/DataStructure/QueueTest.php @@ -2,10 +2,9 @@ declare(strict_types=1); -namespace Psl\DataStructure; +namespace Psl\Tests\Unit\DataStructure; use PHPUnit\Framework\TestCase; -use Psl; use Psl\DataStructure; final class QueueTest extends TestCase @@ -75,8 +74,8 @@ public function testDequeueThrowsWhenTheQueueIsEmpty(): void { $queue = new DataStructure\Queue(); - $this->expectException(Psl\Exception\InvariantViolationException::class); - $this->expectExceptionMessage('Cannot dequeue a node from an empty Queue.'); + $this->expectException(DataStructure\Exception\UnderflowException::class); + $this->expectExceptionMessage('Cannot dequeue a node from an empty queue.'); $queue->dequeue(); } diff --git a/tests/unit/DataStructure/StackTest.php b/tests/unit/DataStructure/StackTest.php index 2eda3472..11139b1e 100644 --- a/tests/unit/DataStructure/StackTest.php +++ b/tests/unit/DataStructure/StackTest.php @@ -5,14 +5,13 @@ namespace Psl\Tests\Unit\DataStructure; use PHPUnit\Framework\TestCase; -use Psl; -use Psl\DataStructure\Stack; +use Psl\DataStructure; final class StackTest extends TestCase { public function testPushAndPop(): void { - $stack = new Stack(); + $stack = new DataStructure\Stack(); $stack->push('hello'); $stack->push('hey'); $stack->push('hi'); @@ -30,7 +29,7 @@ public function testPushAndPop(): void public function testPeek(): void { - $stack = new Stack(); + $stack = new DataStructure\Stack(); static::assertNull($stack->peek()); @@ -38,24 +37,29 @@ public function testPeek(): void static::assertNotNull($stack->peek()); static::assertSame('hello', $stack->peek()); + + static::assertSame('hello', $stack->pop()); + + static::assertNull($stack->peek()); } public function testPopThrowsForEmptyStack(): void { - $stack = new Stack(); + $stack = new DataStructure\Stack(); $stack->push('hello'); static::assertSame('hello', $stack->pop()); + static::assertNull($stack->peek()); - $this->expectException(Psl\Exception\InvariantViolationException::class); - $this->expectExceptionMessage('Cannot pop an item from an empty Stack.'); + $this->expectException(DataStructure\Exception\UnderflowException::class); + $this->expectExceptionMessage('Cannot pop an item from an empty stack.'); $stack->pop(); } public function testPullReturnsNullForEmptyStack(): void { - $stack = new Stack(); + $stack = new DataStructure\Stack(); $stack->push('hello'); static::assertSame('hello', $stack->pull()); @@ -64,10 +68,18 @@ public function testPullReturnsNullForEmptyStack(): void public function testCount(): void { - $stack = new Stack(); + $stack = new DataStructure\Stack(); static::assertSame(0, $stack->count()); $stack->push('hello'); static::assertSame(1, $stack->count()); + $stack->pop(); + static::assertSame(0, $stack->count()); + $stack->push('hello'); + $stack->push('hello'); + $stack->push('hello'); + static::assertSame(3, $stack->count()); + $stack->pop(); + static::assertSame(2, $stack->count()); } } diff --git a/tests/unit/Dict/TakeTest.php b/tests/unit/Dict/TakeTest.php index 8633f4f4..7711609d 100644 --- a/tests/unit/Dict/TakeTest.php +++ b/tests/unit/Dict/TakeTest.php @@ -12,7 +12,9 @@ final class TakeTest extends TestCase public function testTake(): void { $result = Dict\take([-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5], 3); - static::assertSame([-5, -4, -3], $result); + + $result = Dict\take([1, 2], 0); + static::assertSame([], $result); } }