Completed
Push — master ( 7aefdd...988c00 )
by Rudi
02:20
created

Set::reversed()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 0
1
<?php
2
namespace Ds;
3
4
use Error;
5
use OutOfBoundsException;
6
use OutOfRangeException;
7
use Traversable;
8
9
/**
10
 * Set
11
 *
12
 * @package Ds
13
 */
14
final class Set implements \IteratorAggregate, \ArrayAccess, Collection
15
{
16
    use Traits\Collection;
17
18
    const MIN_CAPACITY = Map::MIN_CAPACITY;
19
20
    /**
21
     * @var Map
22
     */
23
    private $table;
24
25
    /**
26
     * Creates a new set using the values of an array or Traversable object.
27
     * The keys of either will not be preserved.
28
     *
29
     * @param array|\Traversable|null $values
30
     */
31
    public function __construct($values = null)
32
    {
33
        $this->table = new Map();
34
35
        if (func_num_args()) {
36
            $this->addAll($values);
37
        }
38
    }
39
40
    private function addAll($values)
41
    {
42
        foreach ($values as $value) {
43
            $this->table[$value] = null;
44
        }
45
    }
46
47
    /**
48
     * Adds zero or more values to the set.
49
     *
50
     * @param mixed ...$values
51
     */
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $values a bit more specific; maybe use array.
Loading history...
52
    public function add(...$values)
53
    {
54
        $this->addAll($values);
55
    }
56
57
    /**
58
     * Ensures that enough memory is allocated for a specified capacity. This
59
     * potentially reduces the number of reallocations as the size increases.
60
     *
61
     * @param int $capacity The number of values for which capacity should be
62
     *                      allocated. Capacity will stay the same if this value
63
     *                      is less than or equal to the current capacity.
64
     */
65
    public function allocate(int $capacity)
66
    {
67
        $this->table->allocate($capacity);
68
    }
69
70
    /**
71
     * Returns the current capacity of the set.
72
     *
73
     * @return int
74
     */
75
    public function capacity(): int
76
    {
77
        return $this->table->capacity();
78
    }
79
80
    /**
81
     * Clear all elements in the Set
82
     */
83
    public function clear()
84
    {
85
        $this->table->clear();
86
    }
87
88
    /**
89
     * Determines whether the set contains all of zero or more values.
90
     *
91
     * @param mixed ...$values
92
     *
93
     * @return bool true if at least one value was provided and the set
94
     *              contains all given values, false otherwise.
95
     */
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $values a bit more specific; maybe use array.
Loading history...
96 View Code Duplication
    public function contains(...$values): bool
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
97
    {
98
        if ( ! $values) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $values of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
99
            return false;
100
        }
101
102
        foreach ($values as $value) {
103
            if ( ! $this->table->hasKey($value)) {
104
                return false;
105
            }
106
        }
107
108
        return true;
109
    }
110
111
    /**
112
     * @inheritDoc
113
     */
114
    public function copy(): \Ds\Collection
115
    {
116
        return new self($this);
117
    }
118
119
    /**
120
     * Returns the number of elements in the Stack
121
     *
122
     * @return int
123
     */
124
    public function count(): int
125
    {
126
        return count($this->table);
127
    }
128
129
    /**
130
     * Creates a new set using values from this set that aren't in another set.
131
     *
132
     * Formally: A \ B = {x ∈ A | x ∉ B}
133
     *
134
     * @param Set $set
135
     *
136
     * @return Set
137
     */
138
    public function diff(Set $set): Set
139
    {
140
        $diff = new self();
141
142
        foreach ($this as $value) {
143
            if ($set->contains($value)) {
144
                continue;
145
            }
146
147
            $diff->add($value);
148
        }
149
150
        return $diff;
151
    }
152
153
    /**
154
     * Creates a new set using values in either this set or in another set,
155
     * but not in both.
156
     *
157
     * Formally: A ⊖ B = {x : x ∈ (A \ B) ∪ (B \ A)}
158
     *
159
     * @param Set $set
160
     *
161
     * @return Set
162
     */
163
    public function xor(Set $set): Set
164
    {
165
        return $this->table->xor($set->table)->keys();
166
    }
167
168
    /**
169
     * Returns a new set containing only the values for which a predicate
170
     * returns true. A boolean test will be used if a predicate is not provided.
171
     *
172
     * @param callable|null $predicate Accepts a value, returns a boolean:
173
     *                                 true : include the value,
174
     *                                 false: skip the value.
175
     *
176
     * @return Set
177
     */
178 View Code Duplication
    public function filter(callable $predicate = null): Set
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
179
    {
180
        $filtered = new self();
181
182
        foreach ($this as $value) {
183
            if ($predicate ? $predicate($value) : $value) {
184
                $filtered->add($value);
185
            }
186
        }
187
188
        return $filtered;
189
    }
190
191
    /**
192
     * Returns the first value in the set.
193
     *
194
     * @return mixed the first value in the set.
195
     */
196
    public function first()
197
    {
198
        return $this->table->first()->key;
199
    }
200
201
    /**
202
     * Returns the value at a specified position in the set.
203
     *
204
     * @param int $position
205
     *
206
     * @return mixed|null
207
     *
208
     * @throws OutOfRangeException
209
     */
210
    public function get(int $position)
211
    {
212
        return $this->table->skip($position)->key;
213
    }
214
215
    /**
216
     * Creates a new set using values common to both this set and another set.
217
     * In other words, returns a copy of this set with all values removed that
218
     * aren't in the other set.
219
     *
220
     * Formally: A ∩ B = {x : x ∈ A ∧ x ∈ B}
221
     *
222
     * @param Set $set
223
     *
224
     * @return Set
225
     */
226
    public function intersect(Set $set): Set
227
    {
228
        return $this->table->intersect($set->table)->keys();
229
    }
230
231
    /**
232
     * @inheritDoc
233
     */
234
    public function isEmpty(): bool
235
    {
236
        return $this->table->isEmpty();
237
    }
238
239
    /**
240
     * Joins all values of the set into a string, adding an optional 'glue'
241
     * between them. Returns an empty string if the set is empty.
242
     *
243
     * @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...
244
     *
245
     * @return string
246
     */
247
    public function join(string $glue = null): string
248
    {
249
        return implode($glue, $this->toArray());
250
    }
251
252
    /**
253
     * Returns the last value in the set.
254
     *
255
     * @return mixed the last value in the set.
256
     */
257
    public function last()
258
    {
259
        return $this->table->last()->key;
260
    }
261
262
    /**
263
     * Iteratively reduces the set to a single value using a callback.
264
     *
265
     * @param callable $callback Accepts the carry and current value, and
266
     *                           returns an updated carry value.
267
     *
268
     * @param mixed|null $initial Optional initial carry value.
269
     *
270
     * @return mixed The carry value of the final iteration, or the initial
271
     *               value if the set was empty.
272
     */
273
    public function reduce(callable $callback, $initial = null)
274
    {
275
        $carry = $initial;
276
277
        foreach ($this as $value) {
278
            $carry = $callback($carry, $value);
279
        }
280
281
        return $carry;
282
    }
283
284
    /**
285
     * Removes zero or more values from the set.
286
     *
287
     * @param mixed ...$values
288
     */
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $values a bit more specific; maybe use array.
Loading history...
289
    public function remove(...$values)
290
    {
291
        foreach ($values as $value) {
292
            $this->table->remove($value, null);
293
        }
294
    }
295
296
    /**
297
     * Reverses the set in-place.
298
     */
299
    public function reverse()
300
    {
301
        $this->table->reverse();
302
    }
303
304
    /**
305
     * Returns a reversed copy of the set.
306
     *
307
     * @return Set
308
     */
309
    public function reversed(): Set
310
    {
311
        $reversed = $this->copy();
312
        $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...
313
314
        return $reversed;
315
    }
316
317
    /**
318
     * Returns a subset of a given length starting at a specified offset.
319
     *
320
     * @param int $offset If the offset is non-negative, the set will start
321
     *                    at that offset in the set. If offset is negative,
322
     *                    the set will start that far from the end.
323
     *
324
     * @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...
325
     *                    set will have up to that many values in it.
326
     *                    If the requested length results in an overflow, only
327
     *                    values up to the end of the set will be included.
328
     *
329
     *                    If a length is given and is negative, the set
330
     *                    will stop that many values from the end.
331
     *
332
     *                    If a length is not provided, the resulting set
333
     *                    will contains all values between the offset and the
334
     *                    end of the set.
335
     *
336
     * @return Set
337
     */
338
    public function slice(int $offset, int $length = null): Set
339
    {
340
        $sliced = new self();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 8 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
341
        $sliced->table = $this->table->slice($offset, $length);
342
343
        return $sliced;
344
    }
345
346
    /**
347
     * Sorts the set in-place, based on an optional callable comparator.
348
     *
349
     * @param callable|null $comparator Accepts two values to be compared.
350
     *                                  Should return the result of a <=> b.
351
     */
352
    public function sort(callable $comparator = null)
353
    {
354
        $this->table->ksort($comparator);
355
    }
356
357
    /**
358
     * Returns a sorted copy of the set, based on an optional callable
359
     * comparator. Natural ordering will be used if a comparator is not given.
360
     *
361
     * @param callable|null $comparator Accepts two values to be compared.
362
     *                                  Should return the result of a <=> b.
363
     *
364
     * @return Set
365
     */
366
    public function sorted(callable $comparator = null): Set
367
    {
368
        $sorted = $this->copy();
369
        $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...
370
371
        return $sorted;
372
    }
373
374
    /**
375
     * @inheritDoc
376
     */
377
    public function toArray(): array
378
    {
379
        return iterator_to_array($this);
380
    }
381
382
    /**
383
     * Returns the sum of all values in the set.
384
     *
385
     * @return int|float The sum of all the values in the set.
386
     */
387
    public function sum()
388
    {
389
        return array_sum($this->toArray());
390
    }
391
392
    /**
393
     * Creates a new set that contains the values of this set as well as the
394
     * values of another set.
395
     *
396
     * Formally: A ∪ B = {x: x ∈ A ∨ x ∈ B}
397
     *
398
     * @param Set $set
399
     *
400
     * @return Set
401
     */
402
    public function union(Set $set): Set
403
    {
404
        $union = new self();
405
406
        foreach ($this as $value) {
407
            $union->add($value);
408
        }
409
410
        foreach ($set as $value) {
411
            $union->add($value);
412
        }
413
414
        return $union;
415
    }
416
417
    /**
418
     * Get iterator
419
     */
420
    public function getIterator()
421
    {
422
        foreach ($this->table as $key => $value) {
423
            yield $key;
424
        }
425
    }
426
427
    /**
428
     * @inheritdoc
429
     *
430
     * @throws OutOfBoundsException
431
     */
432
    public function offsetSet($offset, $value)
433
    {
434
        if ($offset === null) {
435
            $this->add($value);
436
            return;
437
        }
438
439
        throw new OutOfBoundsException();
440
    }
441
442
    /**
443
     * @inheritdoc
444
     */
445
    public function offsetGet($offset)
446
    {
447
        return $this->table->skip($offset)->key;
448
    }
449
450
    /**
451
     * @inheritdoc
452
     *
453
     * @throws Error
454
     */
455
    public function offsetExists($offset)
456
    {
457
        throw new Error();
458
    }
459
460
    /**
461
     * @inheritdoc
462
     *
463
     * @throws Error
464
     */
465
    public function offsetUnset($offset)
466
    {
467
        throw new Error();
468
    }
469
}
470