Rework data structures
This commit is contained in:
parent
0b3e23ebdc
commit
4e02397519
|
@ -29,7 +29,7 @@ use OCC\Basics\Traits\Getter;
|
||||||
use Serializable;
|
use Serializable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract class for a type-sensitive list of items.
|
* A prototype for a type-sensitive, ordered list of items.
|
||||||
*
|
*
|
||||||
* @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
|
* @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
|
||||||
* @package opencultureconsulting/basics
|
* @package opencultureconsulting/basics
|
||||||
|
@ -37,7 +37,7 @@ use Serializable;
|
||||||
* @implements \Iterator
|
* @implements \Iterator
|
||||||
* @implements \Serializable
|
* @implements \Serializable
|
||||||
*/
|
*/
|
||||||
abstract class AbstractStrictList implements Countable, Iterator, Serializable
|
abstract class AbstractList implements Countable, Iterator, Serializable
|
||||||
{
|
{
|
||||||
use Getter;
|
use Getter;
|
||||||
|
|
||||||
|
@ -47,9 +47,9 @@ abstract class AbstractStrictList implements Countable, Iterator, Serializable
|
||||||
protected array $items = [];
|
protected array $items = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Count of iterations.
|
* Current position of iterator.
|
||||||
*/
|
*/
|
||||||
protected int $iterations = 0;
|
protected int $position = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines the allowed types for items.
|
* 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
|
* @param mixed ...$items One or more items to add
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
*/
|
*/
|
||||||
public function append(mixed ...$items): void
|
public function append(mixed ...$items): void
|
||||||
{
|
{
|
||||||
if (!empty($this->allowedTypes)) {
|
if (!empty($this->allowedTypes)) {
|
||||||
foreach ($items as $count => $item) {
|
foreach ($items as $count => $item) {
|
||||||
if (!$this->isAllowedType($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
|
* @see Iterator::current
|
||||||
*
|
*
|
||||||
* @return mixed The current item or NULL if empty
|
* @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.
|
* 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
|
* @see Iterator::key
|
||||||
*
|
*
|
||||||
* @return int The number of the current iteration
|
* @return int The current iterator position
|
||||||
*/
|
*/
|
||||||
public function key(): int
|
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
|
* @see Iterator::next
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function next(): 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.
|
* Reset the iterator position.
|
||||||
* @see Iterator::rewind
|
* @see Iterator::rewind
|
||||||
|
@ -195,7 +193,7 @@ abstract class AbstractStrictList implements Countable, Iterator, Serializable
|
||||||
*/
|
*/
|
||||||
public function rewind(): void
|
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
|
* @see Iterator::valid
|
||||||
*
|
*
|
||||||
* @return bool Whether there are items
|
* @return bool Is there an item at the current position?
|
||||||
*/
|
*/
|
||||||
public function valid(): bool
|
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
|
* @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)
|
* @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)) {
|
||||||
if (array_sum(array_map('is_string', $allowedTypes)) !== count($allowedTypes)) {
|
throw new InvalidArgumentException('Allowed types must be array of strings or empty array.');
|
||||||
throw new InvalidArgumentException('Allowed types must be array of strings or empty array.');
|
|
||||||
}
|
|
||||||
$this->allowedTypes = $allowedTypes;
|
|
||||||
}
|
}
|
||||||
|
$this->allowedTypes = $allowedTypes;
|
||||||
$this->append(...$items);
|
$this->append(...$items);
|
||||||
|
$this->rewind();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -292,7 +291,6 @@ abstract class AbstractStrictList implements Countable, Iterator, Serializable
|
||||||
*/
|
*/
|
||||||
public function __unserialize(array $data): void
|
public function __unserialize(array $data): void
|
||||||
{
|
{
|
||||||
$this->allowedTypes = $data['allowedTypes'] ?? [];
|
$this->__construct($data['items'], $data['allowedTypes']);
|
||||||
$this->items = $data['items'] ?? [];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -27,7 +27,7 @@ use Iterator;
|
||||||
use Serializable;
|
use Serializable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A (type-sensitive) destructive First In, First Out Queue.
|
* A type-sensitive, destructive First In, First Out Queue.
|
||||||
*
|
*
|
||||||
* @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
|
* @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
|
||||||
* @package opencultureconsulting/basics
|
* @package opencultureconsulting/basics
|
||||||
|
@ -35,10 +35,10 @@ use Serializable;
|
||||||
* @implements \Iterator
|
* @implements \Iterator
|
||||||
* @implements \Serializable
|
* @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
|
* @see Iterator::current
|
||||||
*
|
*
|
||||||
* @return mixed The first item or NULL if empty
|
* @return mixed The first item or NULL if empty
|
||||||
|
@ -49,36 +49,29 @@ class StrictQueue extends AbstractStrictList
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dequeue the first item.
|
* Get a single item without removing it.
|
||||||
* Alias of Queue::current
|
|
||||||
*
|
*
|
||||||
* @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.
|
* Check if there is an item left on the queue.
|
||||||
* Alias of Queue::append
|
* @see Iterator::valid
|
||||||
*
|
*
|
||||||
* @param mixed ...$items One or more items to add
|
* @return bool Is there an item on the queue?
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function enqueue(mixed ...$items): void
|
public function valid(): bool
|
||||||
{
|
{
|
||||||
$this->append(...$items);
|
return (bool) $this->count();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
<?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 of items.
|
||||||
|
*
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,10 +35,10 @@ use Serializable;
|
||||||
* @implements \Iterator
|
* @implements \Iterator
|
||||||
* @implements \Serializable
|
* @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
|
* @see Iterator::current
|
||||||
*
|
*
|
||||||
* @return mixed The last item or NULL if empty
|
* @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.
|
* Check if there is an item left on the stack.
|
||||||
* Alias of Stack::append
|
* @see Iterator::valid
|
||||||
*
|
*
|
||||||
* @param mixed ...$items One or more items to add
|
* @return bool Is there an item on the stack?
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function stack(mixed ...$items): void
|
public function valid(): bool
|
||||||
{
|
{
|
||||||
$this->append(...$items);
|
return (bool) $this->count();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unstack the last item.
|
|
||||||
* Alias of Stack::current
|
|
||||||
*
|
|
||||||
* @return mixed The last item or NULL if empty
|
|
||||||
*/
|
|
||||||
public function unstack(): mixed
|
|
||||||
{
|
|
||||||
return $this->current();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue