Skip to content

Commit 9bafaae

Browse files
committed
New methods for ArrayView, refactoring.
1 parent 74138e3 commit 9bafaae

File tree

6 files changed

+488
-343
lines changed

6 files changed

+488
-343
lines changed

src/Interfaces/ArrayViewInterface.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ public function map(callable $mapper): array;
150150
* @param callable(T, U, int): T $mapper Function to transform each pair of elements.
151151
*
152152
* @return array<mixed> New array with transformed elements of this view.
153+
*
154+
* @throws ValueError if the $data is not sequential array.
155+
* @throws SizeError if size of $data not equals to size of the view.
153156
*/
154157
public function mapWith($data, callable $mapper): array;
155158

Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Smoren\ArrayView\Traits;
6+
7+
use Smoren\ArrayView\Exceptions\SizeError;
8+
use Smoren\ArrayView\Exceptions\ValueError;
9+
use Smoren\ArrayView\Interfaces\ArraySelectorInterface;
10+
use Smoren\ArrayView\Interfaces\ArrayViewInterface;
11+
use Smoren\ArrayView\Interfaces\MaskSelectorInterface;
12+
use Smoren\ArrayView\Selectors\MaskSelector;
13+
use Smoren\ArrayView\Util;
14+
use Smoren\ArrayView\Views\ArrayMaskView;
15+
use Smoren\ArrayView\Views\ArrayView;
16+
17+
/**
18+
* Trait providing methods for operation methods of ArrayView.
19+
*
20+
* @template T Type of ArrayView values.
21+
* @template S of string|array<int|bool>|ArrayViewInterface<int|bool>|ArraySelectorInterface Selector type.
22+
*/
23+
trait ArrayViewOperationsTrait
24+
{
25+
/**
26+
* Filters the elements in the view based on a predicate function.
27+
*
28+
* ##### Example
29+
* ```php
30+
* $source = [1, 2, 3, 4, 5, 6];
31+
* $view = ArrayView::toView($source);
32+
*
33+
* $filtered = $view->filter(fn ($x) => $x % 2 === 0);
34+
* $filtered->toArray(); // [2, 4, 6]
35+
*
36+
* $filtered[':'] = [20, 40, 60];
37+
* $filtered->toArray(); // [20, 40, 60]
38+
* $source; // [1, 20, 3, 40, 5, 60]
39+
* ```
40+
*
41+
* @param callable(T, int): bool $predicate Function that returns a boolean value for each element.
42+
*
43+
* @return ArrayMaskView<T> A new view with elements that satisfy the predicate.
44+
*/
45+
public function filter(callable $predicate): ArrayViewInterface
46+
{
47+
return $this->is($predicate)->select($this);
48+
}
49+
50+
/**
51+
* Checks if all elements in the view satisfy a given predicate function.
52+
*
53+
* ##### Example
54+
* ```php
55+
* $source = [1, 2, 3, 4, 5, 6];
56+
* $view = ArrayView::toView($source);
57+
*
58+
* $mask = $view->is(fn ($x) => $x % 2 === 0);
59+
* $mask->getValue(); // [false, true, false, true, false, true]
60+
*
61+
* $view->subview($mask)->toArray(); // [2, 4, 6]
62+
* $view[$mask]; // [2, 4, 6]
63+
*
64+
* $view[$mask] = [20, 40, 60];
65+
* $source; // [1, 20, 3, 40, 5, 60]
66+
* ```
67+
*
68+
* @param callable(T, int): bool $predicate Function that returns a boolean value for each element.
69+
*
70+
* @return MaskSelector Boolean mask for selecting elements that satisfy the predicate.
71+
*
72+
* @see ArrayViewInterface::match() Full synonim.
73+
*/
74+
public function is(callable $predicate): MaskSelectorInterface
75+
{
76+
$data = $this->toArray();
77+
return new MaskSelector(array_map($predicate, $data, array_keys($data)));
78+
}
79+
80+
/**
81+
* Checks if all elements in the view satisfy a given predicate function.
82+
*
83+
* ##### Example
84+
* ```php
85+
* $source = [1, 2, 3, 4, 5, 6];
86+
* $view = ArrayView::toView($source);
87+
*
88+
* $mask = $view->match(fn ($x) => $x % 2 === 0);
89+
* $mask->getValue(); // [false, true, false, true, false, true]
90+
*
91+
* $view->subview($mask)->toArray(); // [2, 4, 6]
92+
* $view[$mask]; // [2, 4, 6]
93+
*
94+
* $view[$mask] = [20, 40, 60];
95+
* $source; // [1, 20, 3, 40, 5, 60]
96+
* ```
97+
*
98+
* @param callable(T, int): bool $predicate Function that returns a boolean value for each element.
99+
*
100+
* @return MaskSelector Boolean mask for selecting elements that satisfy the predicate.
101+
*
102+
* @see ArrayView::match() Full synonim.
103+
*/
104+
public function match(callable $predicate): MaskSelectorInterface
105+
{
106+
return $this->is($predicate);
107+
}
108+
109+
/**
110+
* Compares the elements of the current ArrayView instance with another array or ArrayView
111+
* using the provided comparator function.
112+
*
113+
* ##### Example
114+
* ```php
115+
* $source = [1, 2, 3, 4, 5, 6];
116+
* $view = ArrayView::toView($source);
117+
*
118+
* $data = [6, 5, 4, 3, 2, 1];
119+
*
120+
* $mask = $view->matchWith($data, fn ($lhs, $rhs) => $lhs > $rhs);
121+
* $mask->getValue(); // [false, false, false, true, true, true]
122+
*
123+
* $view->subview($mask)->toArray(); // [4, 5, 6]
124+
* $view[$mask]; // [4, 5, 6]
125+
*
126+
* $view[$mask] = [40, 50, 60];
127+
* $source; // [1, 2, 3, 40, 50, 60]
128+
* ```
129+
*
130+
* @template U The type of the elements in the array for comparison with.
131+
*
132+
* @param array<U>|ArrayViewInterface<U>|U $data The array or ArrayView to compare to.
133+
* @param callable(T, U, int): bool $comparator Function that determines the comparison logic between the elements.
134+
*
135+
* @return MaskSelectorInterface A MaskSelector instance representing the results of the element comparisons.
136+
*
137+
* @throws ValueError if the $data is not sequential array.
138+
* @throws SizeError if size of $data not equals to size of the view.
139+
*
140+
* @see ArrayView::is() Full synonim.
141+
*/
142+
public function matchWith($data, callable $comparator): MaskSelectorInterface
143+
{
144+
$data = $this->checkAndConvertArgument($data);
145+
return new MaskSelector(array_map($comparator, $this->toArray(), $data, array_keys($data)));
146+
}
147+
148+
/**
149+
* Transforms each element of the array using the given callback function.
150+
*
151+
* The callback function receives two parameters: the current element of the array and its index.
152+
*
153+
* ##### Example
154+
* ```php
155+
* $source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
156+
* $subview = ArrayView::toView($source)->subview('::2'); // [1, 3, 5, 7, 9]
157+
*
158+
* $subview->map(fn ($x) => $x * 10); // [10, 30, 50, 70, 90]
159+
* ```
160+
*
161+
* @param callable(T, int): T $mapper Function to transform each element.
162+
*
163+
* @return array<T> New array with transformed elements of this view.
164+
*/
165+
public function map(callable $mapper): array
166+
{
167+
$result = [];
168+
$size = \count($this);
169+
for ($i = 0; $i < $size; $i++) {
170+
/** @var T $item */
171+
$item = $this[$i];
172+
$result[$i] = $mapper($item, $i);
173+
}
174+
return $result;
175+
}
176+
177+
/**
178+
* Transforms each pair of elements from the current array view and the provided data array using the given
179+
* callback function.
180+
*
181+
* The callback function receives three parameters: the current element of the current array view,
182+
* the corresponding element of the data array, and the index.
183+
*
184+
* ##### Example
185+
* ```php
186+
* $source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
187+
* $subview = ArrayView::toView($source)->subview('::2'); // [1, 3, 5, 7, 9]
188+
*
189+
* $data = [9, 27, 45, 63, 81];
190+
*
191+
* $subview->mapWith($data, fn ($lhs, $rhs) => $lhs * $rhs); // [10, 30, 50, 70, 90]
192+
* ```
193+
*
194+
* @template U The type rhs of a binary operation.
195+
*
196+
* @param array<U>|ArrayViewInterface<U>|U $data The rhs values for a binary operation.
197+
* @param callable(T, U, int): T $mapper Function to transform each pair of elements.
198+
*
199+
* @return array<mixed> New array with transformed elements of this view.
200+
*
201+
* @throws ValueError if the $data is not sequential array.
202+
* @throws SizeError if size of $data not equals to size of the view.
203+
*/
204+
public function mapWith($data, callable $mapper): array
205+
{
206+
$data = $this->checkAndConvertArgument($data);
207+
$result = [];
208+
209+
$size = \count($this);
210+
for ($i = 0; $i < $size; $i++) {
211+
/** @var T $lhs */
212+
$lhs = $this[$i];
213+
/** @var U $rhs */
214+
$rhs = $data[$i];
215+
$result[$i] = $mapper($lhs, $rhs, $i);
216+
}
217+
218+
return $result;
219+
}
220+
221+
/**
222+
* Applies a transformation function to each element in the view.
223+
*
224+
* ##### Example
225+
* ```php
226+
* $source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
227+
* $subview = ArrayView::toView($source)->subview('::2'); // [1, 3, 5, 7, 9]
228+
*
229+
* $subview->apply(fn ($x) => $x * 10);
230+
*
231+
* $subview->toArray(); // [10, 30, 50, 70, 90]
232+
* $source; // [10, 2, 30, 4, 50, 6, 70, 8, 90, 10]
233+
* ```
234+
*
235+
* @param callable(T, int): T $mapper Function to transform each element.
236+
*
237+
* @return ArrayView<T> this view.
238+
*/
239+
public function apply(callable $mapper): self
240+
{
241+
$size = \count($this);
242+
for ($i = 0; $i < $size; $i++) {
243+
/** @var T $item */
244+
$item = $this[$i];
245+
$this[$i] = $mapper($item, $i);
246+
}
247+
return $this;
248+
}
249+
250+
/**
251+
* Sets new values for the elements in the view.
252+
*
253+
* ##### Example
254+
* ```php
255+
* $source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
256+
* $subview = ArrayView::toView($source)->subview('::2'); // [1, 3, 5, 7, 9]
257+
*
258+
* $data = [9, 27, 45, 63, 81];
259+
*
260+
* $subview->applyWith($data, fn ($lhs, $rhs) => $lhs * $rhs);
261+
* $subview->toArray(); // [10, 30, 50, 70, 90]
262+
*
263+
* $source; // [10, 2, 30, 4, 50, 6, 70, 8, 90, 10]
264+
* ```
265+
*
266+
* @template U Type of $data items.
267+
*
268+
* @param array<U>|ArrayViewInterface<U> $data
269+
* @param callable(T, U, int): T $mapper
270+
*
271+
* @return ArrayView<T> this view.
272+
*
273+
* @throws ValueError if the $data is not sequential array.
274+
* @throws SizeError if size of $data not equals to size of the view.
275+
*/
276+
public function applyWith($data, callable $mapper): self
277+
{
278+
$data = $this->checkAndConvertArgument($data);
279+
280+
$size = \count($this);
281+
for ($i = 0; $i < $size; $i++) {
282+
/** @var T $lhs */
283+
$lhs = $this[$i];
284+
/** @var U $rhs */
285+
$rhs = $data[$i];
286+
$this[$i] = $mapper($lhs, $rhs, $i);
287+
}
288+
289+
return $this;
290+
}
291+
292+
/**
293+
* Check if the given source array is sequential (indexed from 0 to n-1).
294+
*
295+
* If the array is not sequential, a ValueError is thrown indicating that
296+
* a view cannot be created for a non-sequential array.
297+
*
298+
* @param mixed $source The source array to check for sequential indexing.
299+
*
300+
* @return void
301+
*
302+
* @throws ValueError if the source array is not sequential.
303+
*/
304+
protected function checkSequentialArgument($source): void
305+
{
306+
if ($source instanceof ArrayViewInterface) {
307+
return;
308+
}
309+
310+
if (\is_array($source) && !Util::isArraySequential($source)) {
311+
throw new ValueError('Argument is not sequential.');
312+
}
313+
}
314+
315+
/**
316+
* Util function for checking and converting data argument.
317+
*
318+
* @template U Type of $data items.
319+
*
320+
* @param array<U>|ArrayViewInterface<U>|U $data The rhs values for a binary operation.
321+
*
322+
* @return array<U> converted data.
323+
*/
324+
protected function checkAndConvertArgument($data): array
325+
{
326+
$this->checkSequentialArgument($data);
327+
328+
if ($data instanceof ArrayViewInterface) {
329+
$data = $data->toArray();
330+
} elseif (!\is_array($data)) {
331+
$data = \array_fill(0, \count($this), $data);
332+
}
333+
334+
[$dataSize, $thisSize] = [\count($data), \count($this)];
335+
if ($dataSize !== $thisSize) {
336+
throw new SizeError("Length of values array not equal to view length ({$dataSize} != {$thisSize}).");
337+
}
338+
339+
return $data;
340+
}
341+
}

0 commit comments

Comments
 (0)