diff --git a/src/DataStructures/Set.php b/src/DataStructures/Set.php deleted file mode 100644 index 7eaba35..0000000 --- a/src/DataStructures/Set.php +++ /dev/null @@ -1,111 +0,0 @@ - - * - * 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 . - */ - -declare(strict_types=1); - -namespace OCC\Basics\DataStructures; - -use InvalidArgumentException; - -/** - * A type-sensitive, ordered Set. - * - * @author Sebastian Meyer - * @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; - } -} diff --git a/src/DataStructures/AbstractList.php b/src/DataStructures/StrictList.php similarity index 54% rename from src/DataStructures/AbstractList.php rename to src/DataStructures/StrictList.php index 853efea..800a471 100644 --- a/src/DataStructures/AbstractList.php +++ b/src/DataStructures/StrictList.php @@ -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 * @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 $count => $item) { + if (!$this->isAllowedType($item)) { + throw new InvalidArgumentException('Parameter ' . $count + 1 . ' must be an allowed type, ' . get_debug_type($item) . ' given.'); } } - $this->items = array_merge($this->items, $items); + foreach ($items as $item) { + parent::push($item); + } } /** - * 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']); } } diff --git a/src/DataStructures/Stack.php b/src/DataStructures/StrictQueue.php similarity index 53% rename from src/DataStructures/Stack.php rename to src/DataStructures/StrictQueue.php index c0caf0f..2401859 100644 --- a/src/DataStructures/Stack.php +++ b/src/DataStructures/StrictQueue.php @@ -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 * @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); - } - - /** - * Get a single item without removing it. - * - * @param ?int $offset Optional offset to peek, defaults to last - * - * @return mixed The item or NULL if empty - */ - public function peek(?int $offset = null): mixed - { - if (is_null($offset)) { - return end($this->items) ?? null; + if ($mode > 1) { + throw new RuntimeException('Changing the iterator direction of ' . static::class . ' is prohibited.'); } - $item = array_slice($this->items, $offset, 1); - return $item[0] ?? null; + return parent::setIteratorMode($mode); } /** - * Check if there is an item left on the stack. - * @see Iterator::valid + * Create a type-sensitive, traversable queue of items. * - * @return bool Is there an item on the stack? + * @param iterable $items Initial set of items + * @param string[] $allowedTypes Allowed types of items (optional) */ - public function valid(): bool + public function __construct(iterable $items = [], array $allowedTypes = []) { - return (bool) $this->count(); + parent::__construct($items, $allowedTypes); + $this->setIteratorMode(0); } } diff --git a/src/DataStructures/Queue.php b/src/DataStructures/StrictStack.php similarity index 53% rename from src/DataStructures/Queue.php rename to src/DataStructures/StrictStack.php index bc57ef9..ee90be1 100644 --- a/src/DataStructures/Queue.php +++ b/src/DataStructures/StrictStack.php @@ -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 * @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); - } - - /** - * Get a single item without removing it. - * - * @param ?int $offset Optional offset to peek, defaults to first - * - * @return mixed The item or NULL if empty - */ - public function peek(?int $offset = null): mixed - { - if (is_null($offset)) { - return reset($this->items) ?? null; + if ($mode < 2) { + throw new RuntimeException('Changing the iterator direction of ' . static::class . ' is prohibited.'); } - $item = array_slice($this->items, $offset, 1); - return $item[0] ?? null; + return parent::setIteratorMode($mode); } /** - * Check if there is an item left on the queue. - * @see Iterator::valid + * Create a type-sensitive, traversable stack of items. * - * @return bool Is there an item on the queue? + * @param iterable $items Initial set of items + * @param string[] $allowedTypes Allowed types of items (optional) */ - public function valid(): bool + public function __construct(iterable $items = [], array $allowedTypes = []) { - return (bool) $this->count(); + parent::__construct($items, $allowedTypes); + $this->setIteratorMode(2); } }