Reimplement data structures based on SPL
This commit is contained in:
parent
dc4209e40f
commit
a84046755e
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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']);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue