Skip to content

Commit

Permalink
[DataStructure] Introduce the DataStructure component
Browse files Browse the repository at this point in the history
  • Loading branch information
azjezz committed Sep 26, 2020
1 parent 85fc380 commit d983b7e
Show file tree
Hide file tree
Showing 7 changed files with 506 additions and 3 deletions.
137 changes: 137 additions & 0 deletions src/Psl/DataStructure/PriorityQueue.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<?php

declare(strict_types=1);

namespace Psl\DataStructure;

use Psl;
use Psl\Arr;
use Psl\Math;

/**
* @psalm-template T
*
* @implements PriorityQueueInterface<T>
*/
final class PriorityQueue implements PriorityQueueInterface
{
/**
* @psalm-var array<int, list<T>>
*/
private array $queue = [];

/**
* Adds a node to the queue.
*
* @psalm-param T $node
*/
public function enqueue(int $priority, $node): void
{
$nodes = $this->queue[$priority] ?? [];
$nodes[] = $node;

$this->queue[$priority] = $nodes;
$this->queue = Arr\filter(
$this->queue,
static fn(array $list): bool => Arr\count($list) !== 0
);
}

/**
* Retrieves, but does not remove, the node at the head of this queue,
* or returns null if this queue is empty.
*
* @psalm-return null|T
*/
public function peek()
{
if (0 === $this->count()) {
return null;
}

/** @psalm-suppress MissingThrowsDocblock - we are sure that the queue is not empty. */
return $this->fetch(false);
}

/**
* Retrieves and removes the node at the head of this queue,
* or returns null if this queue is empty.
*
* @psalm-return null|T
*/
public function pull()
{
if (0 === $this->count()) {
return null;
}

/** @psalm-suppress MissingThrowsDocblock - we are sure that the queue is not empty. */
return $this->fetch(true);
}

/**
* Dequeues a node from the queue.
*
* @psalm-return T
*
* @throws Psl\Exception\InvariantViolationException If the Queue is invalid.
*/
public function dequeue()
{
return $this->fetch(true);
}

/**
* @psalm-return T
*
* @throws Psl\Exception\InvariantViolationException If the Queue is invalid.
*/
private function fetch(bool $remove)
{
Psl\invariant(0 !== $this->count(), 'Cannot dequeue a node from an empty Queue.');

// Retrieve the list of priorities.
$priorities = Arr\keys($this->queue);
/**
* Retrieve the highest priority.
*
* @var int $priority
*/
$priority = Math\max($priorities);
// Retrieve the list of nodes with the priority `$priority`.
$nodes = Arr\at($this->queue, $priority);
/**
* Retrieve the first node of the list.
*
* @psalm-suppress MissingThrowsDocblock - we are sure that the list is not empty.
*/
$node = Arr\firstx($nodes);

// Remove the node if we are supposed to.
if ($remove) {
// If the list contained only this node,
// remove the list of nodes with priority `$priority`.
if (1 === Arr\count($nodes)) {
unset($this->queue[$priority]);
} else {
// otherwise, drop the first node.
$this->queue[$priority] = Arr\values(Arr\drop($nodes, 1));
}
}

return $node;
}

/**
* Count the nodes in the queue.
*/
public function count(): int
{
$count = 0;
foreach ($this->queue as $priority => $list) {
$count += Arr\count($list);
}

return $count;
}
}
51 changes: 51 additions & 0 deletions src/Psl/DataStructure/PriorityQueueInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace Psl\DataStructure;

use Psl;
use Countable;

/**
* @template T
*/
interface PriorityQueueInterface extends Countable
{
/**
* Adds a node to the queue.
*
* @psalm-param T $node
*/
public function enqueue(int $priority, $node): void;

/**
* Retrieves, but does not remove, the node at the head of this queue,
* or returns null if this queue is empty.
*
* @psalm-return null|T
*/
public function peek();

/**
* Retrieves and removes the node at the head of this queue,
* or returns null if this queue is empty.
*
* @psalm-return null|T
*/
public function pull();

/**
* Retrieves and removes the node at the head of this queue.
*
* @psalm-return T
*
* @throws Psl\Exception\InvariantViolationException If the Queue is invalid.
*/
public function dequeue();

/**
* Count the nodes in the queue.
*/
public function count(): int;
}
83 changes: 83 additions & 0 deletions src/Psl/DataStructure/Queue.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php

declare(strict_types=1);

namespace Psl\DataStructure;

use Psl;
use Psl\Arr;

/**
* @psalm-template T
*
* @implements QueueInterface<T>
*/
final class Queue implements QueueInterface
{
/**
* @psalm-var list<T>
*/
private array $queue = [];

/**
* Adds a node to the queue.
*
* @psalm-param T $node
*/
public function enqueue($node): void
{
$this->queue[] = $node;
}

/**
* Retrieves, but does not remove, the node at the head of this queue,
* or returns null if this queue is empty.
*
* @psalm-return null|T
*/
public function peek()
{
return Arr\first($this->queue);
}

/**
* Retrieves and removes the node at the head of this queue,
* or returns null if this queue is empty.
*
* @psalm-return null|T
*/
public function pull()
{
if (0 === $this->count()) {
return null;
}

/** @psalm-suppress MissingThrowsDocblock - we are sure that the queue is not empty. */
return $this->dequeue();
}

/**
* Dequeues a node from the queue.
*
* @psalm-return T
*
* @throws Psl\Exception\InvariantViolationException If the Queue is invalid.
*/
public function dequeue()
{
Psl\invariant(0 !== $this->count(), 'Cannot dequeue a node from an empty Queue.');

$node = Arr\firstx($this->queue);
$this->queue = Arr\values(Arr\drop($this->queue, 1));

return $node;
}

/**
* Count the nodes in the queue.
*/
public function count(): int
{
return Arr\count($this->queue);
}
}
51 changes: 51 additions & 0 deletions src/Psl/DataStructure/QueueInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace Psl\DataStructure;

use Psl;
use Countable;

/**
* @template T
*/
interface QueueInterface extends Countable
{
/**
* Adds a node to the queue.
*
* @psalm-param T $node
*/
public function enqueue($node): void;

/**
* Retrieves, but does not remove, the node at the head of this queue,
* or returns null if this queue is empty.
*
* @psalm-return null|T
*/
public function peek();

/**
* Retrieves and removes the node at the head of this queue,
* or returns null if this queue is empty.
*
* @psalm-return null|T
*/
public function pull();

/**
* Retrieves and removes the node at the head of this queue.
*
* @psalm-return T
*
* @throws Psl\Exception\InvariantViolationException If the Queue is invalid.
*/
public function dequeue();

/**
* Count the nodes in the queue.
*/
public function count(): int;
}
6 changes: 3 additions & 3 deletions src/Psl/Iter/Iterator.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public static function create(iterable $iterable): Iterator
/**
* @psalm-var (callable(): Generator<Tsk, Tsv, mixed, void>) $factory
*/
$factory = fn (): Generator => yield from $iterable;
$factory = static fn (): Generator => yield from $iterable;

return new self($factory());
}
Expand All @@ -77,7 +77,7 @@ public static function create(iterable $iterable): Iterator
*/
public function current()
{
Psl\invariant($this->valid(), 'Invalid iterator');
Psl\invariant($this->valid(), 'The Iterator is invalid.');
if (!Arr\contains_key($this->entries, $this->position)) {
$this->progress();
}
Expand Down Expand Up @@ -117,7 +117,7 @@ public function next(): void
*/
public function key()
{
Psl\invariant($this->valid(), 'Invalid iterator');
Psl\invariant($this->valid(), 'The Iterator is invalid.');
if (!Arr\contains_key($this->entries, $this->position)) {
$this->progress();
}
Expand Down
Loading

0 comments on commit d983b7e

Please sign in to comment.