Reimplement data structures based on SPL

This commit is contained in:
Sebastian Meyer 2023-11-12 15:34:08 +01:00
parent dc4209e40f
commit a84046755e
4 changed files with 146 additions and 256 deletions

View File

@ -1,111 +0,0 @@
<?php
/**
* Useful PHP Basics
* Copyright (C) 2023 Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace OCC\Basics\DataStructures;
use InvalidArgumentException;
/**
* A type-sensitive, ordered Set.
*
* @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
* @package opencultureconsulting/basics
* @implements \Countable
* @implements \Iterator
* @implements \Serializable
*/
class Set extends AbstractList
{
/**
* Add a single item.
*
* @param mixed $item The item to add
* @param ?int $offset Optional offset to add, defaults to append
*
* @return void
*
* @throws \InvalidArgumentException
*/
public function add(mixed $item, ?int $offset = null): void
{
if (is_null($offset)) {
$this->append($item);
} elseif (isset($item)) {
if (!$this->isAllowedType($item)) {
throw new InvalidArgumentException('Parameter 1 must be an allowed type, ' . get_debug_type($item) . ' given.');
}
array_splice($this->items, $offset, 0, [$item]);
if ($offset >= 0) {
if ($offset <= $this->position) {
++$this->position;
}
} elseif ($offset < 0) {
if (($offset + $this->count()) <= $this->position) {
++$this->position;
}
}
}
}
/**
* Get a single item.
*
* @param ?int $offset Optional offset to peek, defaults to current
*
* @return mixed The item or NULL if empty
*/
public function peek(?int $offset = null): mixed
{
if (is_null($offset)) {
return $this->current();
}
$item = array_slice($this->items, $offset, 1);
return $item[0] ?? null;
}
/**
* Remove a single item.
*
* @param ?int $offset Optional offset to remove, defauls to current
*
* @return mixed The removed item or NULL if empty
*/
public function remove(?int $offset = null): mixed
{
if (is_null($offset)) {
$offset = $this->position;
}
$item = array_splice($this->items, $offset, 1);
if (isset($item[0])) {
if ($offset >= 0) {
if ($offset <= $this->position) {
--$this->position;
}
} elseif ($offset < 0) {
if (($offset + $this->count()) <= $this->position) {
--$this->position;
}
}
}
return $item[0] ?? null;
}
}

View File

@ -22,35 +22,25 @@ declare(strict_types=1);
namespace OCC\Basics\DataStructures;
use Countable;
use InvalidArgumentException;
use Iterator;
use SplDoublyLinkedList;
use OCC\Basics\Traits\Getter;
use Serializable;
/**
* A prototype for a type-sensitive, ordered list of items.
* A type-sensitive, taversable List.
*
* @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
* @package opencultureconsulting/basics
*
* @implements \ArrayAccess
* @implements \Countable
* @implements \Iterator
* @implements \Serializable
*/
abstract class AbstractList implements Countable, Iterator, Serializable
class StrictList extends SplDoublyLinkedList
{
use Getter;
/**
* The items.
*/
protected array $items = [];
/**
* Current position of iterator.
*/
protected int $position = 0;
/**
* Defines the allowed types for items.
* If empty, all types are allowed.
@ -74,9 +64,28 @@ abstract class AbstractList implements Countable, Iterator, Serializable
protected array $allowedTypes = [];
/**
* Append items.
* Add/insert a new item at the specified index.
* @see SplDoublyLinkedList::add
*
* @param mixed ...$items One or more items to add
* @param int $index The index where the new item is to be inserted
* @param mixed $item The new item for the index
*
* @return void
*
* @throws \InvalidArgumentException
*/
public function add(int $index, mixed $item): void
{
if (!$this->isAllowedType($item)) {
throw new InvalidArgumentException('Parameter 2 must be an allowed type, ' . get_debug_type($item) . ' given.');
}
parent::add($index, $item);
}
/**
* Append items at the end of the list.
*
* @param mixed ...$items One or more items to append
*
* @return void
*
@ -84,57 +93,34 @@ abstract class AbstractList implements Countable, Iterator, Serializable
*/
public function append(mixed ...$items): void
{
if (!empty($this->allowedTypes)) {
foreach ($items as $count => $item) {
if (!$this->isAllowedType($item)) {
throw new InvalidArgumentException('Parameter ' . $count + 1 . ' must be an allowed type, ' . get_debug_type($item) . ' given.');
}
}
foreach ($items as $item) {
parent::push($item);
}
$this->items = array_merge($this->items, $items);
}
/**
* Get the number of items.
* @see Countable::count
* Get the allowed item types.
*
* @return int The number of items
* @return array The list of allowed item types
*/
public function count(): int
public function getAllowedTypes(): array
{
return count($this->items);
return $this->allowedTypes;
}
/**
* Clear all items.
*
* @return void
*/
public function clear(): void
{
$this->items = [];
$this->rewind();
}
/**
* Get the current item.
* @see Iterator::current
*
* @return mixed The current item or NULL if empty
*/
public function current(): mixed
{
return $this->items[$this->position] ?? null;
}
/**
* Check if an item is an allowed type.
* Check if item is an allowed type.
*
* @param mixed $item The item to check
*
* @return bool Whether the item is an allowed type
*/
protected function isAllowedType(mixed $item): bool
public function isAllowedType(mixed $item): bool
{
if (empty($this->allowedTypes)) {
return true;
@ -152,17 +138,6 @@ abstract class AbstractList implements Countable, Iterator, Serializable
return false;
}
/**
* Get the current iterator position.
* @see Iterator::key
*
* @return int The current iterator position
*/
public function key(): int
{
return $this->position;
}
/**
* Magic getter method for $this->allowedTypes.
* @see OCC\Basics\Traits\Getter
@ -175,34 +150,70 @@ abstract class AbstractList implements Countable, Iterator, Serializable
}
/**
* Move iterator to next position.
* @see Iterator::next
* Set the item at the specified index.
* @see ArrayAccess::offsetSet
*
* @param ?int $index The index being set or NULL to append
* @param mixed $item The new item for the index
*
* @return void
*
* @throws \InvalidArgumentException
*/
public function next(): void
public function offsetSet(mixed $index, mixed $item): void
{
++$this->position;
if (!$this->isAllowedType($item)) {
throw new InvalidArgumentException('Parameter 2 must be an allowed type, ' . get_debug_type($item) . ' given.');
}
parent::offsetSet($index, $item);
}
/**
* Reset the iterator position.
* @see Iterator::rewind
* Prepend items at the start of the list.
*
* @param mixed ...$items One or more items to prepend
*
* @return void
*
* @throws \InvalidArgumentException
*/
public function rewind(): void
public function prepend(mixed ...$items): void
{
$this->position = 0;
foreach ($items as $count => $item) {
if (!$this->isAllowedType($item)) {
throw new InvalidArgumentException('Parameter ' . $count + 1 . ' must be an allowed type, ' . get_debug_type($item) . ' given.');
}
}
foreach ($items as $item) {
parent::unshift($item);
}
}
/**
* Push an item at the end of the list.
* @see SplDoublyLinkedList::push
*
* @param mixed $item The item to push
*
* @return void
*
* @throws \InvalidArgumentException
*/
public function push(mixed $item): void
{
if (!$this->isAllowedType($item)) {
throw new InvalidArgumentException('Parameter 1 must be an allowed type, ' . get_debug_type($item) . ' given.');
}
parent::push($item);
}
/**
* Get string representation of $this.
* @see Serializable::serialize
*
* @return ?string String representation
* @return string String representation
*/
public function serialize(): ?string
public function serialize(): string
{
return serialize($this->__serialize());
}
@ -221,30 +232,27 @@ abstract class AbstractList implements Countable, Iterator, Serializable
}
/**
* Check if there is an item at the current position.
* @see Iterator::valid
* Prepend the list with an item.
* @see SplDoublyLinkedList::unshift
*
* @return bool Is there an item at the current position?
*/
public function valid(): bool
{
return isset($this->items[$this->position]);
}
/**
* Reset iterator position after cloning.
* @param mixed $item The item to unshift
*
* @return void
*
* @throws \InvalidArgumentException
*/
public function __clone(): void
public function unshift(mixed $item): void
{
$this->rewind();
if (!$this->isAllowedType($item)) {
throw new InvalidArgumentException('Parameter 1 must be an allowed type, ' . get_debug_type($item) . ' given.');
}
parent::unshift($item);
}
/**
* Create a type-sensitive traversable list of items.
* Create a type-sensitive, traversable list of items.
*
* @param iterable $items Initial list of items
* @param iterable $items Initial set of items
* @param string[] $allowedTypes Allowed types of items (optional)
*
* @throws \InvalidArgumentException
@ -256,7 +264,6 @@ abstract class AbstractList implements Countable, Iterator, Serializable
}
$this->allowedTypes = $allowedTypes;
$this->append(...$items);
$this->rewind();
}
/**
@ -278,7 +285,8 @@ abstract class AbstractList implements Countable, Iterator, Serializable
{
return [
'allowedTypes' => $this->allowedTypes,
'items' => $this->items
'splDoublyLinkedList::flags' => $this->getIteratorMode(),
'splDoublyLinkedList::dllist' => iterator_to_array($this)
];
}
@ -291,6 +299,7 @@ abstract class AbstractList implements Countable, Iterator, Serializable
*/
public function __unserialize(array $data): void
{
$this->__construct($data['items'], $data['allowedTypes']);
$this->__construct($data['splDoublyLinkedList::dllist'], $data['allowedTypes']);
$this->setIteratorMode($data['splDoublyLinkedList::flags']);
}
}

View File

@ -22,52 +22,48 @@ declare(strict_types=1);
namespace OCC\Basics\DataStructures;
use RuntimeException;
/**
* A type-sensitive, destructive Last In, First Out Stack.
* A type-sensitive, taversable First In, First Out Queue (FIFO).
*
* @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
* @package opencultureconsulting/basics
*
* @implements \ArrayAccess
* @implements \Countable
* @implements \Iterator
* @implements \Serializable
*/
class Stack extends AbstractList
class StrictQueue extends StrictList
{
/**
* Get the last item and remove it.
* @see Iterator::current
* Set the mode of iteration.
* @see SplDoublyLinkedList::setIteratorMode
*
* @return mixed The last item or NULL if empty
* @param int $mode The new iterator mode (0 or 1)
*
* @return int The set of flags and modes of iteration
*
* @throws \RuntimeException
*/
public function current(): mixed
public function setIteratorMode(int $mode): int
{
return array_pop($this->items);
if ($mode > 1) {
throw new RuntimeException('Changing the iterator direction of ' . static::class . ' is prohibited.');
}
return parent::setIteratorMode($mode);
}
/**
* Get a single item without removing it.
* Create a type-sensitive, traversable queue of items.
*
* @param ?int $offset Optional offset to peek, defaults to last
*
* @return mixed The item or NULL if empty
* @param iterable $items Initial set of items
* @param string[] $allowedTypes Allowed types of items (optional)
*/
public function peek(?int $offset = null): mixed
public function __construct(iterable $items = [], array $allowedTypes = [])
{
if (is_null($offset)) {
return end($this->items) ?? null;
}
$item = array_slice($this->items, $offset, 1);
return $item[0] ?? null;
}
/**
* Check if there is an item left on the stack.
* @see Iterator::valid
*
* @return bool Is there an item on the stack?
*/
public function valid(): bool
{
return (bool) $this->count();
parent::__construct($items, $allowedTypes);
$this->setIteratorMode(0);
}
}

View File

@ -22,52 +22,48 @@ declare(strict_types=1);
namespace OCC\Basics\DataStructures;
use RuntimeException;
/**
* A type-sensitive, destructive First In, First Out Queue.
* A type-sensitive, taversable Last In, First Out Stack (LIFO).
*
* @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
* @package opencultureconsulting/basics
*
* @implements \ArrayAccess
* @implements \Countable
* @implements \Iterator
* @implements \Serializable
*/
class Queue extends AbstractList
class StrictStack extends StrictList
{
/**
* Get the first item and remove it.
* @see Iterator::current
* Set the mode of iteration.
* @see SplDoublyLinkedList::setIteratorMode
*
* @return mixed The first item or NULL if empty
* @param int $mode The new iterator mode (2 or 3)
*
* @return int The set of flags and modes of iteration
*
* @throws \RuntimeException
*/
public function current(): mixed
public function setIteratorMode(int $mode): int
{
return array_shift($this->items);
if ($mode < 2) {
throw new RuntimeException('Changing the iterator direction of ' . static::class . ' is prohibited.');
}
return parent::setIteratorMode($mode);
}
/**
* Get a single item without removing it.
* Create a type-sensitive, traversable stack of items.
*
* @param ?int $offset Optional offset to peek, defaults to first
*
* @return mixed The item or NULL if empty
* @param iterable $items Initial set of items
* @param string[] $allowedTypes Allowed types of items (optional)
*/
public function peek(?int $offset = null): mixed
public function __construct(iterable $items = [], array $allowedTypes = [])
{
if (is_null($offset)) {
return reset($this->items) ?? null;
}
$item = array_slice($this->items, $offset, 1);
return $item[0] ?? null;
}
/**
* Check if there is an item left on the queue.
* @see Iterator::valid
*
* @return bool Is there an item on the queue?
*/
public function valid(): bool
{
return (bool) $this->count();
parent::__construct($items, $allowedTypes);
$this->setIteratorMode(2);
}
}