items = $args; } public function __get($name) { switch ($name) { case "__items__": return $this->items; case "length": return $this->count(); default: throw new AttributeError("Vector has no attribute called $name."); } } protected function decrementCursor() { if ($this->cursor > 0) { $cursor--; } } public function current() { return $this->items[$this->cursor]; } public function key(): int { return $this->cursor; } public function next() { $this->cursor++; } public function rewind() { $this->cursor = 0; } public function valid(): bool { return $this->offsetExists($this->cursor); } public function offsetExists($idx): bool { if (!is_int($idx)) { throw new AttributeError("Vector keys must be integers."); } return $idx < ($this->count() - 1); } protected function ensureIndexInRange($idx) { if (!$this->offsetExists($idx)) { throw new AttributeError("$idx is outside the range [{$this->count()}] of this vector."); } } public function offsetGet($idx) { $this->ensureIndexInRange($idx); return $this->items[$idx]; } public function offsetSet($idx, $val) { $this->ensureIndexInRange($idx); $this->items[$idx] = $val; } public function offsetUnset($idx) { $this->ensureIndexInRange($idx); $this->splice($idx, 1); if ($this->cursor === $idx) { $this->decrementCursor(); } } public function count(): int { return count($this->items); } public function push(...$args) { return array_push($this->items, ...$args); } public function pop() { if ($this->cursor === $this->count() - 1) { $this->decrementCursor(); } return array_pop($this->items); } public function shift() { if ($this->cursor !== 0) { $this->decrementCursor(); } return array_shift($this->items); } public function unshift(...$args) { return array_unshift($this->items, ...$args); } public function slice(int $offset, $length = null): self { return new self(array_slice($this->items, $offset, $length)); } public function splice(int $offset, int $length = null, $replacement = []): self { if (is_null($length)) { $length = $this->count(); } return new self(array_splice($this->items, $offset, $length, $replacement)); } public function map(callable $cb): self { return new self(array_map($cb, $this->items)); } public function forEach(callable $cb) { array_walk($this->items, $cb); } public function filter(callable $cb): self { return new self(array_filter($this->items, $cb)); } public function some(callable $cb): bool { foreach ($this->items as $item) { if ($cb($item)) { return true; } } return false; } public function every(callable $cb): bool { foreach ($this->items as $item) { if (!$cb($item)) { return false; } } return true; } public function includes($val): bool { return in_array($val, $this->items); } public function join($glue = " "): string { return implode($glue, $this->items); } public function reduce(callable $cb, $initial = null) { return array_reduce($this->items, $cb, $initial); } public function sort(callable $cb = null): self { if (is_null($cb)) { sort($this->items); } else { usort($this->items, $cb); } return $this; } public function reverse(): self { $this->items = array_reverse($this->items); return $this; } public function indexOf($val): int { $loc = array_search($val, $this->items); return ($loc !== false) ? $loc : -1; } public function unique(): self { return new self(array_unique($this->items)); } }