Code Coverage for /src/SciPhp/NdArray/IndexTrait.php

 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
85 / 85
100.00% covered (success)
100.00%
8 / 8
CRAP
100.00% covered (success)
100.00%
1 / 1
IndexTrait
100.00% covered (success)
100.00%
85 / 85
100.00% covered (success)
100.00%
8 / 8
37
100.00% covered (success)
100.00%
1 / 1
 offsetGet
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
6
 offsetSet
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
5
 filterGet
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
4
 filterSet
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
5
 indexFilter
100.00% covered (success)
100.00%
30 / 30
100.00% covered (success)
100.00%
1 / 1
9
 filterRange
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
6
 offsetUnset
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 offsetExists
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1 <?php
2
3 declare(strict_types=1);
4
5 namespace SciPhp\NdArray;
6
7 use SciPhp\Exception\Message;
8 use SciPhp\NdArray;
9 use Webmozart\Assert\Assert;
10
11 /**
12  * Indexing methods for NdArray
13  */
14 trait IndexTrait
15 {
16     /**
17      * Get a view by index
18      *
19      * @param string|int $index
20      * @return mixed
21      */
22     final public function offsetGet($index)
23     {
24         // @todo slicing or indexing switch here
25         if ((is_string($index) || is_int($index))
26              && preg_match(IndexInterface::IDX_PAT_FILTER, (string) $index)
27              && preg_match_all(IndexInterface::IDX_PAT_PARSE, (string) $index, $matches)
28         ) {
29             $params = $this->indexFilter($matches);
30
31             $data = $this->filterGet($params, 0, $this->data);
32
33             return \is_array($data) ? new static($data) : $data;
34         }
35         // @todo filtering with array and NdArray
36
37         return Assert::false($index, Message::UNDEFINED_INDEX);
38     }
39
40     /**
41      * Set a view by index
42      *
43      * @param int|string $index
44      *
45      * @param int|string $value
46      */
47     final public function offsetSet($index, $value): NdArray
48     {
49         if ((is_string($index) || is_int($index))
50                  && preg_match(IndexInterface::IDX_PAT_FILTER, (string) $index)
51                  && preg_match_all(IndexInterface::IDX_PAT_PARSE, (string) $index, $matches)
52         ) {
53             $params = $this->indexFilter($matches);
54
55             $this->filterSet($params, 0, $this->data, $value);
56         } else {
57             return Assert::false($index, Message::UNDEFINED_INDEX);
58         }
59
60         return $this;
61     }
62
63     /**
64      * Get values from an element or a range
65      *
66      * @param array $filter
67      * @param array $data
68      * @return int|float|array $value
69      */
70     final protected function filterGet(array $filter, int $index, ?array &$data = null)
71     {
72         if (! isset($filter['start'][$index])) {
73             return $data;
74         }
75
76         [$start, $stop] = $this->filterRange($filter, $index, count($data));
77
78         $stack = array_map(
79             function ($value) use ($filter, $index) {
80                 return \is_array($value)
81                          ? $this->filterGet($filter, $index + 1, $value)
82                          : $value;
83             },
84             array_slice($data, $start, $stop - $start + 1)
85         );
86
87         return $start === $stop
88             ? $stack[0]
89             : $stack;
90     }
91
92     /**
93      * Assign values to an element or a range
94      *
95      * @param array $filter
96      * @param array $data
97      * @param int|float $value
98      */
99     final protected function filterSet(array $filter, int $index, ?array &$data = null, $value = null): void
100     {
101         if (! isset($filter['start'][$index])) {
102             array_walk_recursive(
103                 $data,
104                 static function (&$item) use ($value): void {
105                     $item = $value;
106                 }
107             );
108
109             return;
110         }
111
112         [$start, $stop] = $this->filterRange($filter, $index, count($data));
113
114         array_walk(
115             $data,
116             function (&$item, $key) use ($filter, $index, $value, $start, $stop): void {
117                 if ($key >= $start && $key <= $stop) {
118                     if (\is_array($item)) {
119                         $this->filterSet($filter, $index + 1, $item, $value);
120                     } else {
121                         $item = $value;
122                     }
123                 }
124             }
125         );
126     }
127
128     /**
129      * Prepare filter values
130      */
131     final protected function indexFilter(array $matches): array
132     {
133         $filter = [];
134
135         array_walk(
136             $matches,
137             static function ($item, $key) use (&$filter): void {
138                 if (! is_int($key)) {
139                     $filter[$key] = $item;
140                 }
141             }
142         );
143
144         $params = [];
145
146         array_walk(
147             $filter['start'],
148             static function ($value, $key) use (&$params, $filter): void {
149                 if ($key === 0) {
150                     $index = sprintf(
151                             '%s%s%s%s',
152                             $value,
153                             $filter['col'][$key],
154                             $filter['stop'][$key],
155                             $filter['comma'][$key]
156                     );
157                     Assert::notEq(
158                         $index,
159                         ',',
160                         "Invalid index syntax. Index={$index}"
161                     );
162                 }
163
164                 if ($value !== ''
165                     || $filter['col'][$key] !== ''
166                     || $filter['stop'][$key] !== ''
167                     || $filter['comma'][$key] !== ''
168                 ) {
169                     $params['start'][] = intval($value);
170                     $params['col'][] = $filter['col'][$key];
171
172                     if ($filter['col'][$key] === ':') {
173                         $stop = $filter['stop'][$key] !== ''
174                             ? intval($filter['stop'][$key])
175                             : 'max';
176                     } else {
177                         $stop = intval($value);
178                     }
179
180                     $params['stop'][] = $stop;
181                     $params['comma'][] = $filter['comma'][$key];
182                 }
183             }
184         );
185
186         return $params;
187     }
188
189     /**
190      * Get range definition
191      *
192      * @return int[$start, $stop]
193      */
194     final protected function filterRange(array $filter, int $index, int $count): array
195     {
196         // eq. '-1' '-1,' '-1:-1,' '-2:-1,'
197         if ($filter['start'][$index] < 0) {
198             $filter['start'][$index] = $count + $filter['start'][$index];
199         }
200
201         // all, eq. ','    ':,' '0:0,'
202         if ($filter['start'][$index] === 0
203           && $filter['stop'][$index] === 'max'
204         ) {
205             $filter['start'][$index] = 0;
206             $filter['stop'][$index] = $count - 1;
207         }
208
209         $start = $filter['start'][$index];
210
211         Assert::range($start, 0, $count - 1);
212
213         if ($filter['stop'][$index] === 'max') {
214             $filter['stop'][$index] = $count - 1;
215         }
216
217         // eq. ':-1,' '2:-2,'
218         if ($filter['stop'][$index] < 0) {
219             $stop = $count + $filter['stop'][$index]; //  - 1;
220         }
221         // eq. ':5,' '2:3,'
222         else { // if ($filter['stop'][$index] >= 0) {
223             $stop = $filter['stop'][$index];
224         }
225
226         Assert::range($stop, $start, $count - 1, 'Stop index must be [%s, %s]. Got %s.');
227
228         return [$start, $stop];
229     }
230
231     /**
232      * Remove a portion of the data array
233      *
234      * @param mixed $offset
235      */
236     final public function offsetUnset($offset): bool
237     {
238         return \is_array(array_splice($this->data, $offset));
239     }
240
241     /**
242      * Check that an index is defined
243      *
244      * @param mixed $offset
245      */
246     final public function offsetExists($offset): bool
247     {
248         return \array_key_exists($offset, $this->data);
249     }
250 }