sciphp/numphp

View on GitHub
src/SciPhp/NdArray/IndexTrait.php

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
<?php

declare(strict_types=1);

namespace SciPhp\NdArray;

use SciPhp\Exception\Message;
use SciPhp\NdArray;
use Webmozart\Assert\Assert;

/**
 * Indexing methods for NdArray
 */
trait IndexTrait
{
    /**
     * Get a view by index
     *
     * @param string|int $index
     * @return mixed
     */
    final public function offsetGet($index)
    {
        // @todo slicing or indexing switch here
        if ((is_string($index) || is_int($index))
             && preg_match(IndexInterface::IDX_PAT_FILTER, (string) $index)
             && preg_match_all(IndexInterface::IDX_PAT_PARSE, (string) $index, $matches)
        ) {
            $params = $this->indexFilter($matches);

            $data = $this->filterGet($params, 0, $this->data);

            return \is_array($data) ? new static($data) : $data;
        }
        // @todo filtering with array and NdArray

        return Assert::false($index, Message::UNDEFINED_INDEX);
    }

    /**
     * Set a view by index
     *
     * @param int|string $index
     *
     * @param int|string $value
     */
    final public function offsetSet($index, $value): NdArray
    {
        if ((is_string($index) || is_int($index))
                 && preg_match(IndexInterface::IDX_PAT_FILTER, (string) $index)
                 && preg_match_all(IndexInterface::IDX_PAT_PARSE, (string) $index, $matches)
        ) {
            $params = $this->indexFilter($matches);

            $this->filterSet($params, 0, $this->data, $value);
        } else {
            return Assert::false($index, Message::UNDEFINED_INDEX);
        }

        return $this;
    }

    /**
     * Get values from an element or a range
     *
     * @param array $filter
     * @param array $data
     * @return int|float|array $value
     */
    final protected function filterGet(array $filter, int $index, ?array &$data = null)
    {
        if (! isset($filter['start'][$index])) {
            return $data;
        }

        [$start, $stop] = $this->filterRange($filter, $index, count($data));

        $stack = array_map(
            function ($value) use ($filter, $index) {
                return \is_array($value)
                         ? $this->filterGet($filter, $index + 1, $value)
                         : $value;
            },
            array_slice($data, $start, $stop - $start + 1)
        );

        return $start === $stop
            ? $stack[0]
            : $stack;
    }

    /**
     * Assign values to an element or a range
     *
     * @param array $filter
     * @param array $data
     * @param int|float $value
     */
    final protected function filterSet(array $filter, int $index, ?array &$data = null, $value = null): void
    {
        if (! isset($filter['start'][$index])) {
            array_walk_recursive(
                $data,
                static function (&$item) use ($value): void {
                    $item = $value;
                }
            );

            return;
        }

        [$start, $stop] = $this->filterRange($filter, $index, count($data));

        array_walk(
            $data,
            function (&$item, $key) use ($filter, $index, $value, $start, $stop): void {
                if ($key >= $start && $key <= $stop) {
                    if (\is_array($item)) {
                        $this->filterSet($filter, $index + 1, $item, $value);
                    } else {
                        $item = $value;
                    }
                }
            }
        );
    }

    /**
     * Prepare filter values
     */
    final protected function indexFilter(array $matches): array
    {
        $filter = [];

        array_walk(
            $matches,
            static function ($item, $key) use (&$filter): void {
                if (! is_int($key)) {
                    $filter[$key] = $item;
                }
            }
        );

        $params = [];

        array_walk(
            $filter['start'],
            static function ($value, $key) use (&$params, $filter): void {
                if ($key === 0) {
                    $index = sprintf(
                            '%s%s%s%s',
                            $value,
                            $filter['col'][$key],
                            $filter['stop'][$key],
                            $filter['comma'][$key]
                    );
                    Assert::notEq(
                        $index,
                        ',',
                        "Invalid index syntax. Index={$index}"
                    );
                }

                if ($value !== ''
                    || $filter['col'][$key] !== ''
                    || $filter['stop'][$key] !== ''
                    || $filter['comma'][$key] !== ''
                ) {
                    $params['start'][] = intval($value);
                    $params['col'][] = $filter['col'][$key];

                    if ($filter['col'][$key] === ':') {
                        $stop = $filter['stop'][$key] !== ''
                            ? intval($filter['stop'][$key])
                            : 'max';
                    } else {
                        $stop = intval($value);
                    }

                    $params['stop'][] = $stop;
                    $params['comma'][] = $filter['comma'][$key];
                }
            }
        );

        return $params;
    }

    /**
     * Get range definition
     *
     * @return int[$start, $stop]
     */
    final protected function filterRange(array $filter, int $index, int $count): array
    {
        // eq. '-1' '-1,' '-1:-1,' '-2:-1,'
        if ($filter['start'][$index] < 0) {
            $filter['start'][$index] = $count + $filter['start'][$index];
        }

        // all, eq. ','    ':,' '0:0,'
        if ($filter['start'][$index] === 0
          && $filter['stop'][$index] === 'max'
        ) {
            $filter['start'][$index] = 0;
            $filter['stop'][$index] = $count - 1;
        }

        $start = $filter['start'][$index];

        Assert::range($start, 0, $count - 1);

        if ($filter['stop'][$index] === 'max') {
            $filter['stop'][$index] = $count - 1;
        }

        // eq. ':-1,' '2:-2,'
        if ($filter['stop'][$index] < 0) {
            $stop = $count + $filter['stop'][$index]; //  - 1;
        }
        // eq. ':5,' '2:3,'
        else { // if ($filter['stop'][$index] >= 0) {
            $stop = $filter['stop'][$index];
        }

        Assert::range($stop, $start, $count - 1, 'Stop index must be [%s, %s]. Got %s.');

        return [$start, $stop];
    }

    /**
     * Remove a portion of the data array
     *
     * @param mixed $offset
     */
    final public function offsetUnset($offset): bool
    {
        return \is_array(array_splice($this->data, $offset));
    }

    /**
     * Check that an index is defined
     *
     * @param mixed $offset
     */
    final public function offsetExists($offset): bool
    {
        return \array_key_exists($offset, $this->data);
    }
}