Completed
Pull Request — master (#43)
by
unknown
08:27
created

Set::reverse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 2
1
<?php
2
namespace Ds;
3
4
use Error;
5
use OutOfBoundsException;
6
use OutOfRangeException;
7
8
/**
9
 * A sequence of unique values.
10
 *
11
 * @package Ds
12
 */
13
final class Set implements \IteratorAggregate, \ArrayAccess, Collection
14
{
15
    use Traits\GenericCollection;
16
17
    const MIN_CAPACITY = Map::MIN_CAPACITY;
18
19
    /**
20
     * @var Map internal map to store the values.
21
     */
22
    private $table;
23
24
    /**
25
     * Creates a new set using the values of an array or Traversable object.
26
     * The keys of either will not be preserved.
27
     *
28
     * @param array|\Traversable|null $values
29
     */
30
    public function __construct($values = null)
31
    {
32
        $this->table = new Map();
33
34
        if (func_num_args()) {
35
            $this->add(...$values);
36
        }
37
    }
38
39
    /**
40
     * Adds zero or more values to the set.
41
     *
42
     * @param mixed ...$values
43
     */
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $values a bit more specific; maybe use array.
Loading history...
44
    public function add(...$values)
45
    {
46
        foreach ($values as $value) {
47
            $this->table->put($value, null);
48
        }
49
    }
50
51
    /**
52
     * Ensures that enough memory is allocated for a specified capacity. This
53
     * potentially reduces the number of reallocations as the size increases.
54
     *
55
     * @param int $capacity The number of values for which capacity should be
56
     *                      allocated. Capacity will stay the same if this value
57
     *                      is less than or equal to the current capacity.
58
     */
59
    public function allocate(int $capacity)
60
    {
61
        $this->table->allocate($capacity);
62
    }
63
64
    /**
65
     * Returns the current capacity of the set.
66
     *
67
     * @return int
68
     */
69
    public function capacity(): int
70
    {
71
        return $this->table->capacity();
72
    }
73
74
    /**
75
     * Clear all elements in the Set
76
     */
77
    public function clear()
78
    {
79
        $this->table->clear();
80
    }
81
82
    /**
83
     * Determines whether the set contains all of zero or more values.
84
     *
85
     * @param mixed ...$values
86
     *
87
     * @return bool true if at least one value was provided and the set
88
     *              contains all given values, false otherwise.
89
     */
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $values a bit more specific; maybe use array.
Loading history...
90
    public function contains(...$values): bool
91
    {
92
        foreach ($values as $value) {
93
            if ( ! $this->table->hasKey($value)) {
94
                return false;
95
            }
96
        }
97
98
        return true;
99
    }
100
101
    /**
102
     * @inheritDoc
103
     */
104
    public function copy(): \Ds\Collection
105
    {
106
        return new self($this);
107
    }
108
109
    /**
110
     * Returns the number of elements in the Stack
111
     *
112
     * @return int
113
     */
114
    public function count(): int
115
    {
116
        return count($this->table);
117
    }
118
119
    /**
120
     * Creates a new set using values from this set that aren't in another set.
121
     *
122
     * Formally: A \ B = {x ∈ A | x ∉ B}
123
     *
124
     * @param Set $set
125
     *
126
     * @return Set
127
     */
128
    public function diff(Set $set): Set
129
    {
130
        return $this->table->diff($set->table)->keys();
131
    }
132
133
    /**
134
     * Creates a new set using values in either this set or in another set,
135
     * but not in both.
136
     *
137
     * Formally: A ⊖ B = {x : x ∈ (A \ B) ∪ (B \ A)}
138
     *
139
     * @param Set $set
140
     *
141
     * @return Set
142
     */
143
    public function xor(Set $set): Set
144
    {
145
        return $this->table->xor($set->table)->keys();
146
    }
147
148
    /**
149
     * Returns a new set containing only the values for which a callback
150
     * returns true. A boolean test will be used if a callback is not provided.
151
     *
152
     * @param callable|null $callback Accepts a value, returns a boolean:
153
     *                                 true : include the value,
154
     *                                 false: skip the value.
155
     *
156
     * @return Set
157
     */
158
    public function filter(callable $callback = null): Set
159
    {
160
        return new self(array_filter($this->toArray(), $callback ?: 'boolval'));
161
    }
162
163
    /**
164
     * Returns the first value in the set.
165
     *
166
     * @return mixed the first value in the set.
167
     */
168
    public function first()
169
    {
170
        return $this->table->first()->key;
171
    }
172
173
    /**
174
     * Returns the value at a specified position in the set.
175
     *
176
     * @param int $position
177
     *
178
     * @return mixed|null
179
     *
180
     * @throws OutOfRangeException
181
     */
182
    public function get(int $position)
183
    {
184
        return $this->table->skip($position)->key;
185
    }
186
187
    /**
188
     * Creates a new set using values common to both this set and another set.
189
     *
190
     * In other words, returns a copy of this set with all values removed that
191
     * aren't in the other set.
192
     *
193
     * Formally: A ∩ B = {x : x ∈ A ∧ x ∈ B}
194
     *
195
     * @param Set $set
196
     *
197
     * @return Set
198
     */
199
    public function intersect(Set $set): Set
200
    {
201
        return $this->table->intersect($set->table)->keys();
202
    }
203
204
    /**
205
     * @inheritDoc
206
     */
207
    public function isEmpty(): bool
208
    {
209
        return $this->table->isEmpty();
210
    }
211
212
    /**
213
     * Joins all values of the set into a string, adding an optional 'glue'
214
     * between them. Returns an empty string if the set is empty.
215
     *
216
     * @param string $glue
0 ignored issues
show
Documentation introduced by
Should the type for parameter $glue not be null|string?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
217
     *
218
     * @return string
219
     */
220
    public function join(string $glue = null): string
221
    {
222
        return implode($glue, $this->toArray());
223
    }
224
225
    /**
226
     * Returns the last value in the set.
227
     *
228
     * @return mixed the last value in the set.
229
     */
230
    public function last()
231
    {
232
        return $this->table->last()->key;
233
    }
234
235
    /**
236
     * Iteratively reduces the set to a single value using a callback.
237
     *
238
     * @param callable $callback Accepts the carry and current value, and
239
     *                           returns an updated carry value.
240
     *
241
     * @param mixed|null $initial Optional initial carry value.
242
     *
243
     * @return mixed The carry value of the final iteration, or the initial
244
     *               value if the set was empty.
245
     */
246
    public function reduce(callable $callback, $initial = null)
247
    {
248
        $carry = $initial;
249
250
        foreach ($this as $value) {
251
            $carry = $callback($carry, $value);
252
        }
253
254
        return $carry;
255
    }
256
257
    /**
258
     * Removes zero or more values from the set.
259
     *
260
     * @param mixed ...$values
261
     */
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $values a bit more specific; maybe use array.
Loading history...
262
    public function remove(...$values)
263
    {
264
        foreach ($values as $value) {
265
            $this->table->remove($value, null);
266
        }
267
    }
268
269
    /**
270
     * Reverses the set in-place.
271
     */
272
    public function reverse()
273
    {
274
        $this->table->reverse();
275
    }
276
277
    /**
278
     * Returns a reversed copy of the set.
279
     *
280
     * @return Set
281
     */
282
    public function reversed(): Set
283
    {
284
        $reversed = $this->copy();
285
        $reversed->table->reverse();
0 ignored issues
show
Bug introduced by
Accessing table on the interface Ds\Collection suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
286
287
        return $reversed;
288
    }
289
290
    /**
291
     * Returns a subset of a given length starting at a specified offset.
292
     *
293
     * @param int $offset If the offset is non-negative, the set will start
294
     *                    at that offset in the set. If offset is negative,
295
     *                    the set will start that far from the end.
296
     *
297
     * @param int $length If a length is given and is positive, the resulting
0 ignored issues
show
Documentation introduced by
Should the type for parameter $length not be null|integer?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
298
     *                    set will have up to that many values in it.
299
     *                    If the requested length results in an overflow, only
300
     *                    values up to the end of the set will be included.
301
     *
302
     *                    If a length is given and is negative, the set
303
     *                    will stop that many values from the end.
304
     *
305
     *                    If a length is not provided, the resulting set
306
     *                    will contains all values between the offset and the
307
     *                    end of the set.
308
     *
309
     * @return Set
310
     */
311
    public function slice(int $offset, int $length = null): Set
312
    {
313
        $sliced = new self();
314
        $sliced->table = $this->table->slice($offset, $length);
315
316
        return $sliced;
317
    }
318
319
    /**
320
     * Sorts the set in-place, based on an optional callable comparator.
321
     *
322
     * @param callable|null $comparator Accepts two values to be compared.
323
     *                                  Should return the result of a <=> b.
324
     */
325
    public function sort(callable $comparator = null)
326
    {
327
        $this->table->ksort($comparator);
328
    }
329
330
    /**
331
     * Returns a sorted copy of the set, based on an optional callable
332
     * comparator. Natural ordering will be used if a comparator is not given.
333
     *
334
     * @param callable|null $comparator Accepts two values to be compared.
335
     *                                  Should return the result of a <=> b.
336
     *
337
     * @return Set
338
     */
339
    public function sorted(callable $comparator = null): Set
340
    {
341
        $sorted = $this->copy();
342
        $sorted->table->ksort($comparator);
0 ignored issues
show
Bug introduced by
Accessing table on the interface Ds\Collection suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
343
344
        return $sorted;
345
    }
346
347
    /**
348
     * Returns the result of adding all given values to the set.
349
     *
350
     * @param array|\Traversable $values
351
     *
352
     * @return \Ds\Set
353
     */
354
    public function merge($values): Set
355
    {
356
        $merged = $this->copy();
357
358
        foreach ($values as $value) {
359
            $merged->add($value);
360
        }
361
362
        return $merged;
363
    }
364
365
    /**
366
     * @inheritDoc
367
     */
368
    public function toArray(): array
369
    {
370
        return iterator_to_array($this);
371
    }
372
373
    /**
374
     * Returns the sum of all values in the set.
375
     *
376
     * @return int|float The sum of all the values in the set.
377
     */
378
    public function sum()
379
    {
380
        return array_sum($this->toArray());
381
    }
382
383
    /**
384
     * Creates a new set that contains the values of this set as well as the
385
     * values of another set.
386
     *
387
     * Formally: A ∪ B = {x: x ∈ A ∨ x ∈ B}
388
     *
389
     * @param Set $set
390
     *
391
     * @return Set
392
     */
393
    public function union(Set $set): Set
394
    {
395
        $union = new self();
396
397
        foreach ($this as $value) {
398
            $union->add($value);
399
        }
400
401
        foreach ($set as $value) {
402
            $union->add($value);
403
        }
404
405
        return $union;
406
    }
407
408
    /**
409
     * Get iterator
410
     */
411
    public function getIterator()
412
    {
413
        foreach ($this->table as $key => $value) {
414
            yield $key;
415
        }
416
    }
417
418
    /**
419
     * @inheritdoc
420
     *
421
     * @throws OutOfBoundsException
422
     */
423
    public function offsetSet($offset, $value)
424
    {
425
        if ($offset === null) {
426
            $this->add($value);
427
            return;
428
        }
429
430
        throw new OutOfBoundsException();
431
    }
432
433
    /**
434
     * @inheritdoc
435
     */
436
    public function offsetGet($offset)
437
    {
438
        return $this->table->skip($offset)->key;
439
    }
440
441
    /**
442
     * @inheritdoc
443
     *
444
     * @throws Error
445
     */
446
    public function offsetExists($offset)
447
    {
448
        throw new Error();
449
    }
450
451
    /**
452
     * @inheritdoc
453
     *
454
     * @throws Error
455
     */
456
    public function offsetUnset($offset)
457
    {
458
        throw new Error();
459
    }
460
}
461