1 | <?php |
||
2 | declare(strict_types = 1); |
||
3 | |||
4 | namespace Innmind\Immutable\Sequence; |
||
5 | |||
6 | use Innmind\Immutable\{ |
||
7 | Map, |
||
8 | Sequence, |
||
9 | Str, |
||
10 | Type, |
||
11 | ValidateArgument, |
||
12 | Exception\OutOfBoundException, |
||
13 | Exception\LogicException, |
||
14 | Exception\ElementNotFound, |
||
15 | Exception\CannotGroupEmptyStructure, |
||
16 | }; |
||
17 | |||
18 | final class Primitive implements Implementation |
||
19 | { |
||
20 | private string $type; |
||
21 | private ValidateArgument $validate; |
||
22 | private array $values; |
||
23 | private ?int $size; |
||
24 | |||
25 | 214 | public function __construct(string $type, ...$values) |
|
26 | { |
||
27 | 214 | $this->type = $type; |
|
28 | 214 | $this->validate = Type::of($type); |
|
29 | 214 | $this->values = $values; |
|
30 | 214 | } |
|
31 | |||
32 | 3 | public function type(): string |
|
33 | { |
||
34 | 3 | return $this->type; |
|
35 | } |
||
36 | |||
37 | 153 | public function size(): int |
|
38 | { |
||
39 | 153 | return $this->size ?? $this->size = \count($this->values); |
|
40 | } |
||
41 | |||
42 | 1 | public function count(): int |
|
43 | { |
||
44 | 1 | return $this->size(); |
|
45 | } |
||
46 | |||
47 | 124 | public function toArray(): array |
|
48 | { |
||
49 | 124 | return $this->values; |
|
50 | } |
||
51 | |||
52 | 28 | public function get(int $index) |
|
53 | { |
||
54 | 28 | if (!$this->has($index)) { |
|
55 | 2 | throw new OutOfBoundException; |
|
56 | } |
||
57 | |||
58 | 26 | return $this->values[$index]; |
|
59 | } |
||
60 | |||
61 | 4 | public function diff(Implementation $sequence): self |
|
62 | { |
||
63 | return $this->filter(static function($value) use ($sequence): bool { |
||
64 | 4 | return !$sequence->contains($value); |
|
65 | 4 | }); |
|
66 | } |
||
67 | |||
68 | 82 | public function distinct(): self |
|
69 | { |
||
70 | 82 | return $this->reduce( |
|
71 | 82 | $this->clear(), |
|
72 | static function(self $values, $value): self { |
||
73 | 53 | if ($values->contains($value)) { |
|
74 | 8 | return $values; |
|
75 | } |
||
76 | |||
77 | 53 | return $values->add($value); |
|
78 | 82 | } |
|
79 | ); |
||
80 | } |
||
81 | |||
82 | 6 | public function drop(int $size): self |
|
83 | { |
||
84 | 6 | $self = $this->clear(); |
|
85 | 6 | $self->values = \array_slice($this->values, $size); |
|
86 | |||
87 | 6 | return $self; |
|
88 | } |
||
89 | |||
90 | 2 | public function dropEnd(int $size): self |
|
91 | { |
||
92 | 2 | $self = $this->clear(); |
|
93 | 2 | $self->values = \array_slice($this->values, 0, $this->size() - $size); |
|
94 | |||
95 | 2 | return $self; |
|
96 | } |
||
97 | |||
98 | 7 | public function equals(Implementation $sequence): bool |
|
99 | { |
||
100 | 7 | return $this->values === $sequence->toArray(); |
|
101 | } |
||
102 | |||
103 | 23 | public function filter(callable $predicate): self |
|
104 | { |
||
105 | 23 | $self = $this->clear(); |
|
106 | 23 | $self->values = \array_values(\array_filter( |
|
107 | 23 | $this->values, |
|
108 | 23 | $predicate |
|
109 | )); |
||
110 | |||
111 | 23 | return $self; |
|
112 | } |
||
113 | |||
114 | 4 | public function foreach(callable $function): void |
|
115 | { |
||
116 | 4 | foreach ($this->values as $value) { |
|
117 | 4 | $function($value); |
|
118 | } |
||
119 | 4 | } |
|
120 | |||
121 | 6 | public function groupBy(callable $discriminator): Map |
|
122 | { |
||
123 | 6 | if ($this->size() === 0) { |
|
124 | 2 | throw new CannotGroupEmptyStructure; |
|
125 | } |
||
126 | |||
127 | 4 | $map = null; |
|
128 | |||
129 | 4 | foreach ($this->values as $value) { |
|
130 | 4 | $key = $discriminator($value); |
|
131 | |||
132 | 4 | if ($map === null) { |
|
133 | 4 | $map = Map::of( |
|
134 | 4 | Type::determine($key), |
|
135 | 4 | Sequence::class |
|
136 | ); |
||
137 | } |
||
138 | |||
139 | 4 | if ($map->contains($key)) { |
|
140 | 4 | $map = $map->put( |
|
141 | 4 | $key, |
|
142 | 4 | $map->get($key)->add($value) |
|
143 | ); |
||
144 | } else { |
||
145 | 4 | $map = $map->put($key, Sequence::of($this->type, $value)); |
|
146 | } |
||
147 | } |
||
148 | |||
149 | 4 | return $map; |
|
0 ignored issues
–
show
Bug
Best Practice
introduced
by
Loading history...
|
|||
150 | } |
||
151 | |||
152 | 5 | public function first() |
|
153 | { |
||
154 | 5 | if ($this->size() === 0) { |
|
155 | 1 | throw new OutOfBoundException; |
|
156 | } |
||
157 | |||
158 | 4 | return $this->values[0]; |
|
159 | } |
||
160 | |||
161 | 5 | public function last() |
|
162 | { |
||
163 | 5 | if ($this->size() === 0) { |
|
164 | 1 | throw new OutOfBoundException; |
|
165 | } |
||
166 | |||
167 | 4 | return $this->values[$this->size() - 1]; |
|
168 | } |
||
169 | |||
170 | 97 | public function contains($element): bool |
|
171 | { |
||
172 | 97 | return \in_array($element, $this->values, true); |
|
173 | } |
||
174 | |||
175 | 23 | public function indexOf($element): int |
|
176 | { |
||
177 | 23 | $index = \array_search($element, $this->values, true); |
|
178 | |||
179 | 23 | if ($index === false) { |
|
180 | 1 | throw new ElementNotFound($element); |
|
181 | } |
||
182 | |||
183 | 22 | return $index; |
|
0 ignored issues
–
show
|
|||
184 | } |
||
185 | |||
186 | 4 | public function indices(): self |
|
187 | { |
||
188 | 4 | if ($this->size() === 0) { |
|
189 | 2 | return new self('int'); |
|
190 | } |
||
191 | |||
192 | 2 | return new self('int', ...\range(0, $this->size() - 1)); |
|
193 | } |
||
194 | |||
195 | 5 | public function map(callable $function): self |
|
196 | { |
||
197 | $function = function($value) use ($function) { |
||
198 | 5 | $value = $function($value); |
|
199 | 5 | ($this->validate)($value, 1); |
|
200 | |||
201 | 3 | return $value; |
|
202 | 5 | }; |
|
203 | |||
204 | 5 | $self = clone $this; |
|
205 | 5 | $self->values = \array_map($function, $this->values); |
|
206 | |||
207 | 3 | return $self; |
|
208 | } |
||
209 | |||
210 | 2 | public function pad(int $size, $element): self |
|
211 | { |
||
212 | 2 | $self = $this->clear(); |
|
213 | 2 | $self->values = \array_pad($this->values, $size, $element); |
|
214 | |||
215 | 2 | return $self; |
|
216 | } |
||
217 | |||
218 | 4 | public function partition(callable $predicate): Map |
|
219 | { |
||
220 | 4 | $truthy = []; |
|
221 | 4 | $falsy = []; |
|
222 | |||
223 | 4 | foreach ($this->values as $value) { |
|
224 | 4 | if ($predicate($value) === true) { |
|
225 | 4 | $truthy[] = $value; |
|
226 | } else { |
||
227 | 4 | $falsy[] = $value; |
|
228 | } |
||
229 | } |
||
230 | |||
231 | 4 | $true = Sequence::of($this->type, ...$truthy); |
|
232 | 4 | $false = Sequence::of($this->type, ...$falsy); |
|
233 | |||
234 | 4 | return Map::of('bool', Sequence::class) |
|
235 | 4 | (true, $true) |
|
236 | 4 | (false, $false); |
|
237 | } |
||
238 | |||
239 | 15 | public function slice(int $from, int $until): self |
|
240 | { |
||
241 | 15 | $self = $this->clear(); |
|
242 | 15 | $self->values = \array_slice( |
|
243 | 15 | $this->values, |
|
244 | 15 | $from, |
|
245 | 15 | $until - $from, |
|
246 | ); |
||
247 | |||
248 | 15 | return $self; |
|
249 | } |
||
250 | |||
251 | 2 | public function splitAt(int $index): Sequence |
|
252 | { |
||
253 | 2 | return Sequence::of(Sequence::class) |
|
254 | 2 | ->add(Sequence::of($this->type, ...$this->slice(0, $index)->toArray())) |
|
255 | 2 | ->add(Sequence::of($this->type, ...$this->slice($index, $this->size())->toArray())); |
|
256 | } |
||
257 | |||
258 | 6 | public function take(int $size): self |
|
259 | { |
||
260 | 6 | return $this->slice(0, $size); |
|
261 | } |
||
262 | |||
263 | 2 | public function takeEnd(int $size): self |
|
264 | { |
||
265 | 2 | return $this->slice($this->size() - $size, $this->size()); |
|
266 | } |
||
267 | |||
268 | 9 | public function append(Implementation $sequence): self |
|
269 | { |
||
270 | 9 | $self = $this->clear(); |
|
271 | 9 | $self->values = \array_merge($this->values, $sequence->toArray()); |
|
272 | |||
273 | 9 | return $self; |
|
274 | } |
||
275 | |||
276 | 15 | public function intersect(Implementation $sequence): self |
|
277 | { |
||
278 | return $this->filter(static function($value) use ($sequence): bool { |
||
279 | 15 | return $sequence->contains($value); |
|
280 | 15 | }); |
|
281 | } |
||
282 | |||
283 | 12 | public function join(string $separator): Str |
|
284 | { |
||
285 | 12 | return Str::of(\implode($separator, $this->values)); |
|
286 | } |
||
287 | |||
288 | 140 | public function add($element): self |
|
289 | { |
||
290 | 140 | $self = clone $this; |
|
291 | 140 | $self->values[] = $element; |
|
292 | 140 | $self->size = $this->size() + 1; |
|
293 | |||
294 | 140 | return $self; |
|
295 | } |
||
296 | |||
297 | 4 | public function sort(callable $function): self |
|
298 | { |
||
299 | 4 | $self = clone $this; |
|
300 | 4 | \usort($self->values, $function); |
|
301 | |||
302 | 4 | return $self; |
|
303 | } |
||
304 | |||
305 | 173 | public function reduce($carry, callable $reducer) |
|
306 | { |
||
307 | 173 | return \array_reduce($this->values, $reducer, $carry); |
|
308 | } |
||
309 | |||
310 | 106 | public function clear(): Implementation |
|
311 | { |
||
312 | 106 | return new self($this->type); |
|
313 | } |
||
314 | |||
315 | 3 | public function reverse(): self |
|
316 | { |
||
317 | 3 | $self = clone $this; |
|
318 | 3 | $self->values = \array_reverse($this->values); |
|
319 | |||
320 | 3 | return $self; |
|
321 | } |
||
322 | |||
323 | 5 | public function empty(): bool |
|
324 | { |
||
325 | 5 | return !$this->has(0); |
|
326 | } |
||
327 | |||
328 | 33 | private function has(int $index): bool |
|
329 | { |
||
330 | 33 | return \array_key_exists($index, $this->values); |
|
331 | } |
||
332 | } |
||
333 |