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);
}
}