Rework data structures

This commit is contained in:
Sebastian Meyer 2023-11-10 22:36:25 +01:00
parent 0b3e23ebdc
commit 4e02397519
4 changed files with 178 additions and 83 deletions

View File

@ -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'] ?? [];
} }
} }

View File

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

111
src/DataStructures/Set.php Normal file
View File

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

View File

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