Skip to content

Commit 894a05e

Browse files
feat: add AbstractList to Core-package
1 parent e61b42f commit 894a05e

File tree

2 files changed

+688
-0
lines changed

2 files changed

+688
-0
lines changed

src/Quant/Core/AbstractList.php

Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
<?php
2+
3+
/**
4+
* This file is part of the quant project.
5+
*
6+
* (c) 2023 Thorsten Suckow-Homberg <[email protected]>
7+
*
8+
* For full copyright and license information, please consult the LICENSE-file distributed
9+
* with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Quant\Core;
15+
16+
use ArrayAccess;
17+
use Quant\Core\Contract\Arrayable;
18+
use Countable;
19+
use Iterator;
20+
use OutOfBoundsException;
21+
use Quant\Core\Contract\Comparable;
22+
use Quant\Core\Contract\Equatable;
23+
use TypeError;
24+
25+
/**
26+
* List supporting Generic types.
27+
* Each class deriving from AbstractList must provide information about the type maintained
28+
* with instances of this list via `getType`.
29+
* In addition to the interfaces implemented by this class, additional methods are provided
30+
* that help with filtering or looking up entries: #findBy, #peek#, #map
31+
*
32+
* @template TValue
33+
* @implements Iterator<int, TValue>
34+
* @implements ArrayAccess<int, TValue>
35+
*/
36+
abstract class AbstractList implements Arrayable, ArrayAccess, Iterator, Countable, Equatable
37+
{
38+
/**
39+
* @var array<int, TValue>
40+
*/
41+
protected array $data = [];
42+
43+
/**
44+
* \Iterator Interface
45+
* @var int
46+
*/
47+
protected int $position = 0;
48+
49+
50+
/**
51+
* Constructor.
52+
* Final to allow new static();
53+
*
54+
* @see make
55+
*/
56+
final public function __construct()
57+
{
58+
}
59+
60+
61+
/**
62+
* Factory method for easily creating instances of the implementing class.
63+
*
64+
* @param mixed ...$items
65+
*
66+
* @return static
67+
*/
68+
public static function make(...$items): static
69+
{
70+
$self = new static();
71+
72+
foreach ($items as $item) {
73+
$self[] = $item;
74+
}
75+
76+
return $self;
77+
}
78+
79+
80+
/**
81+
* Returns the class name of the entity-type this list should maintain
82+
* entries of.
83+
*
84+
* @return string
85+
*/
86+
abstract public function getType(): string;
87+
88+
89+
/**
90+
* Applies the map function to this data and returns **this** list.
91+
*
92+
* @param callable $mapFn The callable to pass to the callback submitted to
93+
* array_map()
94+
*
95+
* @return static
96+
*/
97+
public function map(callable $mapFn): static
98+
{
99+
array_map($mapFn, $this->data);
100+
101+
return $this;
102+
}
103+
104+
105+
/**
106+
* Returns the entry in this list given the callback function.
107+
*
108+
* @param callable $findFn A callback. Return true in the function to indicate a match. First match will
109+
* be returned. The callback is passed the current entry.
110+
*
111+
* @return ?TValue
112+
*/
113+
public function findBy(callable $findFn): mixed
114+
{
115+
foreach ($this->data as $resource) {
116+
if ($findFn($resource) === true) {
117+
return $resource;
118+
}
119+
}
120+
121+
return null;
122+
}
123+
124+
125+
/**
126+
* Returns the element at the head of the AbstractList, or null if the list is empty.
127+
*
128+
* @return ?TValue
129+
*/
130+
public function peek(): mixed
131+
{
132+
$count = count($this->data);
133+
return !$count ? null : $this->data[$count - 1];
134+
}
135+
136+
137+
public function equals(Equatable $target): bool
138+
{
139+
$thisClass = get_class($this);
140+
141+
if (!($target instanceof $thisClass)) {
142+
return false;
143+
}
144+
145+
/**
146+
* @var AbstractList<TValue> $td
147+
*/
148+
$td = $target->toArray();
149+
if (count($td) !== count($this)) {
150+
return false;
151+
}
152+
153+
$type = $this->getType();
154+
$isEquatable = is_a($type, Equatable::class, true);
155+
$isComparable = is_a($type, Comparable::class, true);
156+
157+
foreach ($td as $i => $entity) {
158+
if ($isEquatable) {
159+
if ($entity->equals($this[$i]) === false) {
160+
return false;
161+
}
162+
} elseif ($isComparable) {
163+
if ($entity->compareTo($this[$i]) !== 0) {
164+
return false;
165+
}
166+
} else {
167+
if (!$this->compareItems($this[$i], $entity)) {
168+
return false;
169+
}
170+
}
171+
}
172+
173+
return true;
174+
}
175+
176+
177+
/**
178+
* Method called by the abstract list if containing items are neither Equatable nor Comparable.
179+
* Override to implement comparator.
180+
*
181+
* @param mixed $a
182+
* @param mixed $b
183+
* @return bool
184+
*/
185+
protected function compareItems(mixed $a, mixed $b): bool
186+
{
187+
return $a === $b;
188+
}
189+
190+
/**
191+
* @param mixed $offset
192+
* @param mixed $value
193+
* @return void
194+
*
195+
* @throws OutOfBoundsException
196+
*/
197+
protected function doInsert(mixed $offset, mixed $value)
198+
{
199+
if (!is_null($offset) && !is_int($offset)) {
200+
throw new OutOfBoundsException(
201+
"expected integer key for \"offset\", " .
202+
"but got type: " . (gettype($offset))
203+
);
204+
}
205+
206+
if (is_null($offset)) {
207+
$this->data[] = $value;
208+
} else {
209+
$this->data[$offset] = $value;
210+
}
211+
}
212+
213+
/**
214+
* @param mixed $value
215+
* @return bool
216+
*
217+
* @throws TypeError
218+
*/
219+
protected function assertTypeFor(mixed $value): bool
220+
{
221+
$entityType = $this->getType();
222+
223+
// instanceof has higher precedence, so
224+
// (!$value instanceof $entityType)
225+
// would also be a valid expression
226+
if (!($value instanceof $entityType)) {
227+
/** @var object $value */
228+
throw new TypeError(
229+
"Expected type \"$entityType\" for value-argument, got " . gettype($value)
230+
);
231+
}
232+
233+
return true;
234+
}
235+
236+
237+
// -------------------------
238+
// ArrayAccess Interface
239+
// -------------------------
240+
241+
/**
242+
* @throws TypeError|OutOfBoundsException if $value is not of the type defined
243+
* with this getType, or f $offset is not an int
244+
*/
245+
public function offsetSet(mixed $offset, mixed $value): void
246+
{
247+
$this->assertTypeFor($value);
248+
$this->doInsert($offset, $value);
249+
}
250+
251+
252+
public function offsetExists($offset): bool
253+
{
254+
return isset($this->data[$offset]);
255+
}
256+
257+
public function offsetUnset($offset): void
258+
{
259+
unset($this->data[$offset]);
260+
}
261+
262+
263+
public function offsetGet($offset): mixed
264+
{
265+
return $this->data[$offset] ?? null;
266+
}
267+
268+
269+
// --------------------------
270+
// Iterator Interface
271+
// --------------------------
272+
273+
public function rewind(): void
274+
{
275+
$this->position = 0;
276+
}
277+
278+
public function key(): int
279+
{
280+
return $this->position;
281+
}
282+
283+
public function current(): mixed
284+
{
285+
return $this->data[$this->position];
286+
}
287+
288+
public function next(): void
289+
{
290+
$this->position++;
291+
}
292+
293+
/**
294+
* @inheritdoc
295+
*/
296+
public function valid(): bool
297+
{
298+
return isset($this->data[$this->position]);
299+
}
300+
301+
// --------------------------
302+
// Iterator Interface
303+
// --------------------------
304+
305+
/**
306+
* @return int
307+
*/
308+
public function count(): int
309+
{
310+
return count($this->data);
311+
}
312+
313+
314+
// --------------------------
315+
// Arrayable interface
316+
// --------------------------
317+
318+
/**
319+
* @return array<mixed, TValue>
320+
*/
321+
public function toArray(): array
322+
{
323+
return $this->data;
324+
}
325+
}

0 commit comments

Comments
 (0)