From 4e02397519416bfcd7c7bf41e64ccd931662f20c Mon Sep 17 00:00:00 2001 From: Sebastian Meyer Date: Fri, 10 Nov 2023 22:36:25 +0100 Subject: [PATCH] Rework data structures --- ...bstractStrictList.php => AbstractList.php} | 66 +++++------ .../{StrictQueue.php => Queue.php} | 43 +++---- src/DataStructures/Set.php | 111 ++++++++++++++++++ .../{StrictStack.php => Stack.php} | 41 +++---- 4 files changed, 178 insertions(+), 83 deletions(-) rename src/DataStructures/{AbstractStrictList.php => AbstractList.php} (78%) rename src/DataStructures/{StrictQueue.php => Queue.php} (63%) create mode 100644 src/DataStructures/Set.php rename src/DataStructures/{StrictStack.php => Stack.php} (66%) diff --git a/src/DataStructures/AbstractStrictList.php b/src/DataStructures/AbstractList.php similarity index 78% rename from src/DataStructures/AbstractStrictList.php rename to src/DataStructures/AbstractList.php index 5921eab..853efea 100644 --- a/src/DataStructures/AbstractStrictList.php +++ b/src/DataStructures/AbstractList.php @@ -29,7 +29,7 @@ use OCC\Basics\Traits\Getter; use Serializable; /** - * Abstract class for a type-sensitive list of items. + * A prototype for a type-sensitive, ordered list of items. * * @author Sebastian Meyer * @package opencultureconsulting/basics @@ -37,7 +37,7 @@ use Serializable; * @implements \Iterator * @implements \Serializable */ -abstract class AbstractStrictList implements Countable, Iterator, Serializable +abstract class AbstractList implements Countable, Iterator, Serializable { use Getter; @@ -47,9 +47,9 @@ abstract class AbstractStrictList implements Countable, Iterator, Serializable protected array $items = []; /** - * Count of iterations. + * Current position of iterator. */ - protected int $iterations = 0; + protected int $position = 0; /** * Defines the allowed types for items. @@ -79,13 +79,15 @@ abstract class AbstractStrictList implements Countable, Iterator, Serializable * @param mixed ...$items One or more items to add * * @return void + * + * @throws \InvalidArgumentException */ public function append(mixed ...$items): void { if (!empty($this->allowedTypes)) { foreach ($items as $count => $item) { if (!$this->isAllowedType($item)) { - throw new InvalidArgumentException('Item ' . $count + 1 . ' must be an allowed type, ' . get_debug_type($item) . ' given.'); + throw new InvalidArgumentException('Parameter ' . $count + 1 . ' must be an allowed type, ' . get_debug_type($item) . ' given.'); } } } @@ -115,12 +117,15 @@ abstract class AbstractStrictList implements Countable, Iterator, Serializable } /** - * Get and remove the current item. + * Get the current item. * @see Iterator::current * * @return mixed The current item or NULL if empty */ - abstract public function current(): mixed; + public function current(): mixed + { + return $this->items[$this->position] ?? null; + } /** * Check if an item is an allowed type. @@ -148,14 +153,14 @@ abstract class AbstractStrictList implements Countable, Iterator, Serializable } /** - * Get the number of the current iteration. + * Get the current iterator position. * @see Iterator::key * - * @return int The number of the current iteration + * @return int The current iterator position */ public function key(): int { - return $this->iterations; + return $this->position; } /** @@ -170,23 +175,16 @@ abstract class AbstractStrictList implements Countable, Iterator, Serializable } /** - * Count the next iteration. + * Move iterator to next position. * @see Iterator::next * * @return void */ public function next(): void { - ++$this->iterations; + ++$this->position; } - /** - * Get the current item without removing it. - * - * @return mixed The current item or NULL if empty - */ - abstract public function peek(): mixed; - /** * Reset the iterator position. * @see Iterator::rewind @@ -195,7 +193,7 @@ abstract class AbstractStrictList implements Countable, Iterator, Serializable */ public function rewind(): void { - $this->iterations = 0; + $this->position = 0; } /** @@ -223,18 +221,18 @@ abstract class AbstractStrictList implements Countable, Iterator, Serializable } /** - * Check if there are any items. + * Check if there is an item at the current position. * @see Iterator::valid * - * @return bool Whether there are items + * @return bool Is there an item at the current position? */ public function valid(): bool { - return (bool) $this->items; + return isset($this->items[$this->position]); } /** - * Reset iteration counter after cloning. + * Reset iterator position after cloning. * * @return void */ @@ -244,20 +242,21 @@ abstract class AbstractStrictList implements Countable, Iterator, Serializable } /** - * Create a (type-sensitive) traversable set of items. + * Create a type-sensitive traversable list of items. * - * @param array $items Initial set of items + * @param iterable $items Initial list of items * @param string[] $allowedTypes Allowed types of items (optional) + * + * @throws \InvalidArgumentException */ - public function __construct(array $items = [], array $allowedTypes = []) + public function __construct(iterable $items = [], array $allowedTypes = []) { - if (!empty($allowedTypes)) { - if (array_sum(array_map('is_string', $allowedTypes)) !== count($allowedTypes)) { - throw new InvalidArgumentException('Allowed types must be array of strings or empty array.'); - } - $this->allowedTypes = $allowedTypes; + if (array_sum(array_map('is_string', $allowedTypes)) !== count($allowedTypes)) { + throw new InvalidArgumentException('Allowed types must be array of strings or empty array.'); } + $this->allowedTypes = $allowedTypes; $this->append(...$items); + $this->rewind(); } /** @@ -292,7 +291,6 @@ abstract class AbstractStrictList implements Countable, Iterator, Serializable */ public function __unserialize(array $data): void { - $this->allowedTypes = $data['allowedTypes'] ?? []; - $this->items = $data['items'] ?? []; + $this->__construct($data['items'], $data['allowedTypes']); } } diff --git a/src/DataStructures/StrictQueue.php b/src/DataStructures/Queue.php similarity index 63% rename from src/DataStructures/StrictQueue.php rename to src/DataStructures/Queue.php index 7fad134..48235e2 100644 --- a/src/DataStructures/StrictQueue.php +++ b/src/DataStructures/Queue.php @@ -27,7 +27,7 @@ use Iterator; use Serializable; /** - * A (type-sensitive) destructive First In, First Out Queue. + * A type-sensitive, destructive First In, First Out Queue. * * @author Sebastian Meyer * @package opencultureconsulting/basics @@ -35,10 +35,10 @@ use Serializable; * @implements \Iterator * @implements \Serializable */ -class StrictQueue extends AbstractStrictList +class Queue extends AbstractList { /** - * Get and remove the first item. + * Get the first item and remove it. * @see Iterator::current * * @return mixed The first item or NULL if empty @@ -49,36 +49,29 @@ class StrictQueue extends AbstractStrictList } /** - * Dequeue the first item. - * Alias of Queue::current + * Get a single item without removing it. * - * @return mixed The first item or NULL if empty + * @param ?int $offset Optional offset to peek, defaults to first + * + * @return mixed The item or NULL if empty */ - public function dequeue(): mixed + public function peek(?int $offset = null): mixed { - return $this->current(); + if (is_null($offset)) { + return reset($this->items) ?? null; + } + $item = array_slice($this->items, $offset, 1); + return $item[0] ?? null; } /** - * Enqueue items. - * Alias of Queue::append + * Check if there is an item left on the queue. + * @see Iterator::valid * - * @param mixed ...$items One or more items to add - * - * @return void + * @return bool Is there an item on the queue? */ - public function enqueue(mixed ...$items): void + public function valid(): bool { - $this->append(...$items); - } - - /** - * Get the first item without removing it. - * - * @return mixed The first item or NULL if empty - */ - public function peek(): mixed - { - return $this->items[array_key_first($this->items)] ?? null; + return (bool) $this->count(); } } diff --git a/src/DataStructures/Set.php b/src/DataStructures/Set.php new file mode 100644 index 0000000..f08fe33 --- /dev/null +++ b/src/DataStructures/Set.php @@ -0,0 +1,111 @@ + + * + * 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 of items. + * + * @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/StrictStack.php b/src/DataStructures/Stack.php similarity index 66% rename from src/DataStructures/StrictStack.php rename to src/DataStructures/Stack.php index 5a21813..84198d6 100644 --- a/src/DataStructures/StrictStack.php +++ b/src/DataStructures/Stack.php @@ -35,10 +35,10 @@ use Serializable; * @implements \Iterator * @implements \Serializable */ -class StrictStack extends AbstractStrictList +class StrictStack extends AbstractList { /** - * Get and remove the last item. + * Get the last item and remove it. * @see Iterator::current * * @return mixed The last item or NULL if empty @@ -49,36 +49,29 @@ class StrictStack extends AbstractStrictList } /** - * Get the last item without removing it. + * Get a single item without removing it. * - * @return mixed The first item or NULL if empty + * @param ?int $offset Optional offset to peek, defaults to last + * + * @return mixed The item or NULL if empty */ - public function peek(): mixed + public function peek(?int $offset = null): mixed { - return $this->items[array_key_last($this->items)] ?? null; + if (is_null($offset)) { + return end($this->items) ?? null; + } + $item = array_slice($this->items, $offset, 1); + return $item[0] ?? null; } /** - * Stack items. - * Alias of Stack::append + * Check if there is an item left on the stack. + * @see Iterator::valid * - * @param mixed ...$items One or more items to add - * - * @return void + * @return bool Is there an item on the stack? */ - public function stack(mixed ...$items): void + public function valid(): bool { - $this->append(...$items); - } - - /** - * Unstack the last item. - * Alias of Stack::current - * - * @return mixed The last item or NULL if empty - */ - public function unstack(): mixed - { - return $this->current(); + return (bool) $this->count(); } }