Viewing file: GenericSequence.php (9.41 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
<?php namespace Ds\Traits;
use Ds\Sequence; use OutOfRangeException; use UnderflowException;
/** * Common functionality of all structures that implement 'Sequence'. Because the * polyfill's only goal is to achieve consistent behaviour, all sequences will * share the same implementation using an array array. * * @package Ds\Traits * * @template TValue */ trait GenericSequence { /** * @var array internal array used to store the values of the sequence. * * @psalm-var array<TValue> */ private $array = [];
/** * @param iterable $values * * @psalm-param iterable<TValue> $values */ public function __construct(iterable $values = []) { foreach ($values as $value) { $this->push($value); }
$this->capacity = max( $values === null ? 0 : count($values), $this::MIN_CAPACITY ); }
/** * @return list<TValue> */ public function toArray(): array { return $this->array; }
/** * @psalm-param callable(TValue): TValue $callback */ public function apply(callable $callback) { foreach ($this->array as &$value) { $value = $callback($value); } }
/** * @template TValue2 * @psalm-param iterable<TValue2> $values * @psalm-return Sequence<TValue|TValue2> */ public function merge($values): Sequence { $copy = $this->copy(); $copy->push(...$values); return $copy; }
/** * */ public function count(): int { return count($this->array); }
/** * @psalm-param TValue ...$values */ public function contains(...$values): bool { foreach ($values as $value) { if ($this->find($value) === null) { return false; } }
return true; }
/** * @psalm-param (callable(TValue): bool)|null $callback * @psalm-return Sequence<TValue> */ public function filter(callable $callback = null): Sequence { return new self(array_filter($this->array, $callback ?: 'boolval')); }
/** * @return int|null * * @psalm-param TValue $value */ public function find($value) { $offset = array_search($value, $this->array, true);
return $offset === false ? null : $offset; }
/** * @throws \UnderflowException if the sequence is empty. * * @psalm-return TValue */ public function first() { if ($this->isEmpty()) { throw new UnderflowException(); }
return $this->array[0]; }
/** * @throws \OutOfRangeException if the index is not in the range [0, size-1] * * @psalm-return TValue */ public function get(int $index) { if ( ! $this->validIndex($index)) { throw new OutOfRangeException(); }
return $this->array[$index]; }
/** * @throws \OutOfRangeException if the index is not in the range [0, n] * * @psalm-param TValue ...$values */ public function insert(int $index, ...$values) { if ( ! $this->validIndex($index) && $index !== count($this)) { throw new OutOfRangeException(); }
array_splice($this->array, $index, 0, $values); $this->checkCapacity(); }
/** * */ public function join(string $glue = null): string { return implode($glue ?? '', $this->array); }
/** * @throws \UnderflowException if the sequence is empty. * * @psalm-return TValue */ public function last() { if ($this->isEmpty()) { throw new UnderflowException(); }
return $this->array[count($this) - 1]; }
/** * @template TNewValue * @psalm-param callable(TValue): TNewValue $callback * @psalm-return Sequence<TNewValue> */ public function map(callable $callback): Sequence { return new self(array_map($callback, $this->array)); }
/** * @throws \UnderflowException if the sequence is empty. * @psalm-return TValue */ public function pop() { if ($this->isEmpty()) { throw new UnderflowException(); }
$value = array_pop($this->array); $this->checkCapacity();
return $value; }
/** * @psalm-param TValue ...$values */ public function push(...$values) { $this->ensureCapacity($this->count() + count($values));
foreach ($values as $value) { $this->array[] = $value; } }
/** * @template TCarry * @psalm-param callable(TCarry, TValue): TCarry $callback * @psalm-param TCarry $initial * @psalm-return TCarry */ public function reduce(callable $callback, $initial = null) { return array_reduce($this->array, $callback, $initial); }
/** * @throws \OutOfRangeException if the index is not in the range [0, size-1] * * @psalm-return TValue */ public function remove(int $index) { if ( ! $this->validIndex($index)) { throw new OutOfRangeException(); }
$value = array_splice($this->array, $index, 1, null)[0]; $this->checkCapacity();
return $value; }
/** * */ public function reverse() { $this->array = array_reverse($this->array); }
/** * @psalm-return Sequence<TValue> */ public function reversed(): Sequence { return new self(array_reverse($this->array)); }
/** * Converts negative or large rotations into the minimum positive number * of rotations required to rotate the sequence by a given $r. */ private function normalizeRotations(int $r) { $n = count($this);
if ($n < 2) return 0; if ($r < 0) return $n - (abs($r) % $n);
return $r % $n; }
/** * */ public function rotate(int $rotations) { for ($r = $this->normalizeRotations($rotations); $r > 0; $r--) { array_push($this->array, array_shift($this->array)); } }
/** * @throws \OutOfRangeException if the index is not in the range [0, size-1] * * @psalm-param TValue $value */ public function set(int $index, $value) { if ( ! $this->validIndex($index)) { throw new OutOfRangeException(); }
$this->array[$index] = $value; }
/** * @throws \UnderflowException if the sequence was empty. * * @psalm-return TValue */ public function shift() { if ($this->isEmpty()) { throw new UnderflowException(); }
$value = array_shift($this->array); $this->checkCapacity();
return $value; }
/** * @psalm-return Sequence<TValue> */ public function slice(int $offset, int $length = null): Sequence { if (func_num_args() === 1) { $length = count($this); }
return new self(array_slice($this->array, $offset, $length)); }
/** * @psalm-param (callable(TValue, TValue): int)|null $comparator */ public function sort(callable $comparator = null) { if ($comparator) { usort($this->array, $comparator); } else { sort($this->array); } }
/** * @psalm-param (callable(TValue, TValue): int)|null $comparator * @psalm-return Sequence<TValue> */ public function sorted(callable $comparator = null): Sequence { $copy = $this->copy(); $copy->sort($comparator); return $copy; }
/** * @return int|float */ public function sum() { return array_sum($this->array); }
/** * @psalm-param TValue ...$values */ public function unshift(...$values) { if ($values) { $this->array = array_merge($values, $this->array); $this->checkCapacity(); } }
/** * */ private function validIndex(int $index) { return $index >= 0 && $index < count($this); }
/** * */ #[\ReturnTypeWillChange] public function getIterator() { foreach ($this->array as $value) { yield $value; } }
/** * @inheritdoc */ public function clear() { $this->array = []; $this->capacity = self::MIN_CAPACITY; }
/** * @inheritdoc */ #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { if ($offset === null) { $this->push($value); } else { $this->set($offset, $value); } }
/** * @inheritdoc */ #[\ReturnTypeWillChange] public function &offsetGet($offset) { if ( ! $this->validIndex($offset)) { throw new OutOfRangeException(); }
return $this->array[$offset]; }
/** * @inheritdoc */ #[\ReturnTypeWillChange] public function offsetUnset($offset) { if (is_integer($offset) && $this->validIndex($offset)) { $this->remove($offset); } }
/** * @inheritdoc */ #[\ReturnTypeWillChange] public function offsetExists($offset) { return is_integer($offset) && $this->validIndex($offset) && $this->get($offset) !== null; } }
|