Completed
Push — master ( 7482d8...b6686e )
by Rudi
02:24
created

Set::sort()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 7
rs 9.4285
cc 1
eloc 4
nc 1
nop 1
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 $internal;
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
     *
30
     * Should an integer be provided the Set will allocate the memory capacity
31
     * to the size of $values.
32
     *
33
     * @param array|\Traversable|int|null $values
34
     */
35 View Code Duplication
    public function __construct($values = null)
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...
36
    {
37
        $this->internal = new Map();
38
39
        if (is_array($values) || $values instanceof Traversable) {
40
            $this->addAll($values);
41
        } elseif (is_integer($values)) {
42
            $this->allocate($values);
43
        }
44
    }
45
46
    /**
47
     * Adds zero or more values to the set.
48
     *
49
     * @param mixed ...$values
50
     */
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $values a bit more specific; maybe use array.
Loading history...
51
    public function add(...$values)
52
    {
53
        foreach ($values as $value) {
54
            $this->internal[$value] = null;
55
        }
56
    }
57
58
    /**
59
     * Adds all values in an array or iterable object to the set.
60
     *
61
     * @param array|\Traversable $values
62
     */
63
    public function addAll($values)
64
    {
65
        if ($values) {
66
            foreach ($values as $value) {
67
                $this->internal[$value] = null;
68
            }
69
        }
70
    }
71
72
    /**
73
     * Ensures that enough memory is allocated for a specified capacity. This
74
     * potentially reduces the number of reallocations as the size increases.
75
     *
76
     * @param int $capacity The number of values for which capacity should be
77
     *                      allocated. Capacity will stay the same if this value
78
     *                      is less than or equal to the current capacity.
79
     */
80
    public function allocate(int $capacity)
81
    {
82
        $this->internal->allocate($capacity);
83
    }
84
85
    /**
86
     * Returns the current capacity of the set.
87
     *
88
     * @return int
89
     */
90
    public function capacity(): int
91
    {
92
        return $this->internal->capacity();
93
    }
94
95
    /**
96
     * Clear all elements in the Set
97
     */
98
    public function clear()
99
    {
100
        $this->internal->clear();
101
    }
102
103
    /**
104
     * Determines whether the set contains all of zero or more values.
105
     *
106
     * @param mixed ...$values
107
     *
108
     * @return bool true if at least one value was provided and the set
109
     *              contains all given values, false otherwise.
110
     */
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $values a bit more specific; maybe use array.
Loading history...
111 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...
112
    {
113
        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...
114
            return false;
115
        }
116
117
        foreach ($values as $value) {
118
            if ( ! $this->internal->containsKey($value)) {
119
                return false;
120
            }
121
        }
122
123
        return true;
124
    }
125
126
    /**
127
     * @inheritDoc
128
     */
129
    public function copy()
130
    {
131
        return new Set($this);
132
    }
133
134
    /**
135
     * Returns the number of elements in the Stack
136
     *
137
     * @return int
138
     */
139
    public function count(): int
140
    {
141
        return count($this->internal);
142
    }
143
144
    /**
145
     * Creates a new set using values from this set that aren't in another set.
146
     *
147
     * Formally: A \ B = {x ∈ A | x ∉ B}
148
     *
149
     * @param Set $set
150
     *
151
     * @return Set
152
     */
153
    public function diff(Set $set): Set
154
    {
155
        $diff = new Set();
156
157
        foreach ($this as $value) {
158
            if ($set->contains($value)) {
159
                continue;
160
            }
161
162
            $diff->add($value);
163
        }
164
165
        return $diff;
166
    }
167
168
    /**
169
     * Creates a new set using values in either this set or in another set,
170
     * but not in both.
171
     *
172
     * Formally: A ⊖ B = {x : x ∈ (A \ B) ∪ (B \ A)}
173
     *
174
     * @param Set $set
175
     *
176
     * @return Set
177
     */
178
    public function xor(Set $set): Set
179
    {
180
        return $this->internal->xor($set->internal)->keys();
181
    }
182
183
    /**
184
     * Returns a new set containing only the values for which a predicate
185
     * returns true. A boolean test will be used if a predicate is not provided.
186
     *
187
     * @param callable|null $predicate Accepts a value, returns a boolean:
188
     *                                 true : include the value,
189
     *                                 false: skip the value.
190
     *
191
     * @return Set
192
     */
193 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...
194
    {
195
        $filtered = new Set();
196
197
        foreach ($this as $value) {
198
            if ($predicate ? $predicate($value) : $value) {
199
                $filtered->add($value);
200
            }
201
        }
202
203
        return $filtered;
204
    }
205
206
    /**
207
     * Returns the first value in the set.
208
     *
209
     * @return mixed the first value in the set.
210
     */
211
    public function first()
212
    {
213
        return $this->internal->first()->key;
214
    }
215
216
    /**
217
     * Returns the value at a specified position in the set.
218
     *
219
     * @param int $position
220
     *
221
     * @return mixed|null
222
     *
223
     * @throws OutOfRangeException
224
     */
225
    public function get(int $position)
226
    {
227
        return $this->internal->skip($position)->key;
228
    }
229
230
    /**
231
     * Creates a new set using values common to both this set and another set.
232
     * In other words, returns a copy of this set with all values removed that
233
     * aren't in the other set.
234
     *
235
     * Formally: A ∩ B = {x : x ∈ A ∧ x ∈ B}
236
     *
237
     * @param Set $set
238
     *
239
     * @return Set
240
     */
241
    public function intersect(Set $set): Set
242
    {
243
        return $this->internal->intersect($set->internal)->keys();
244
    }
245
246
    /**
247
     * @inheritDoc
248
     */
249
    public function isEmpty(): bool
250
    {
251
        return $this->internal->isEmpty();
252
    }
253
254
    /**
255
     * Joins all values of the set into a string, adding an optional 'glue'
256
     * between them. Returns an empty string if the set is empty.
257
     *
258
     * @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...
259
     *
260
     * @return string
261
     */
262
    public function join(string $glue = null): string
263
    {
264
        return implode($glue, $this->toArray());
265
    }
266
267
    /**
268
     * Returns the last value in the set.
269
     *
270
     * @return mixed the last value in the set.
271
     */
272
    public function last()
273
    {
274
        return $this->internal->last()->key;
275
    }
276
277
    /**
278
     * Iteratively reduces the set to a single value using a callback.
279
     *
280
     * @param callable $callback Accepts the carry and current value, and
281
     *                           returns an updated carry value.
282
     *
283
     * @param mixed|null $initial Optional initial carry value.
284
     *
285
     * @return mixed The carry value of the final iteration, or the initial
286
     *               value if the set was empty.
287
     */
288
    public function reduce(callable $callback, $initial = null)
289
    {
290
        $carry = $initial;
291
292
        foreach ($this as $value) {
293
            $carry = $callback($carry, $value);
294
        }
295
296
        return $carry;
297
    }
298
299
    /**
300
     * Removes zero or more values from the set.
301
     *
302
     * @param mixed ...$values
303
     */
0 ignored issues
show
Documentation introduced by
Consider making the type for parameter $values a bit more specific; maybe use array.
Loading history...
304
    public function remove(...$values)
305
    {
306
        foreach ($values as $value) {
307
            $this->internal->remove($value, null);
308
        }
309
    }
310
311
    /**
312
     * Returns a reversed copy of the set.
313
     *
314
     * @return Set
315
     */
316
    public function reverse(): Set
317
    {
318
        $reversed = new Set();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 11 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...
319
        $reversed->internal = $this->internal->reverse();
320
321
        return $reversed;
322
    }
323
324
    /**
325
     * Returns a subset of a given length starting at a specified offset.
326
     *
327
     * @param int $offset If the offset is non-negative, the set will start
328
     *                    at that offset in the set. If offset is negative,
329
     *                    the set will start that far from the end.
330
     *
331
     * @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...
332
     *                    set will have up to that many values in it.
333
     *                    If the requested length results in an overflow, only
334
     *                    values up to the end of the set will be included.
335
     *
336
     *                    If a length is given and is negative, the set
337
     *                    will stop that many values from the end.
338
     *
339
     *                    If a length is not provided, the resulting set
340
     *                    will contains all values between the offset and the
341
     *                    end of the set.
342
     *
343
     * @return Set
344
     */
345
    public function slice(int $offset, int $length = null): Set
346
    {
347
        $sliced = new Set();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 11 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...
348
        $sliced->internal = $this->internal->slice($offset, $length);
349
350
        return $sliced;
351
    }
352
353
    /**
354
     * Returns a sorted copy of the set, based on an optional callable
355
     * comparator. Natural ordering will be used if a comparator is not given.
356
     *
357
     * @param callable|null $comparator Accepts two values to be compared.
358
     *                                  Should return the result of a <=> b.
359
     *
360
     * @return Set
361
     */
362
    public function sort(callable $comparator = null): Set
363
    {
364
        $set = new Set();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 11 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...
365
        $set->internal = $this->internal->sort($comparator);
366
367
        return $set;
368
    }
369
370
    /**
371
     * @inheritDoc
372
     */
373
    public function toArray(): array
374
    {
375
        return iterator_to_array($this);
376
    }
377
378
    /**
379
     * Creates a new set that contains the values of this set as well as the
380
     * values of another set.
381
     *
382
     * Formally: A ∪ B = {x: x ∈ A ∨ x ∈ B}
383
     *
384
     * @param Set $set
385
     *
386
     * @return Set
387
     */
388
    public function union(Set $set): Set
389
    {
390
        $union = new Set();
391
392
        foreach ($this as $value) {
393
            $union->add($value);
394
        }
395
396
        foreach ($set as $value) {
397
            $union->add($value);
398
        }
399
400
        return $union;
401
    }
402
403
    /**
404
     * Get iterator
405
     */
406
    public function getIterator()
407
    {
408
        foreach ($this->internal as $key => $value) {
409
            yield $key;
410
        }
411
    }
412
413
    /**
414
     * @inheritdoc
415
     *
416
     * @throws OutOfBoundsException
417
     */
418
    public function offsetSet($offset, $value)
419
    {
420
        if ($offset === null) {
421
            $this->add($value);
422
            return;
423
        }
424
425
        throw new OutOfBoundsException();
426
    }
427
428
    /**
429
     * @inheritdoc
430
     */
431
    public function offsetGet($offset)
432
    {
433
        return $this->internal->skip($offset)->key;
434
    }
435
436
    /**
437
     * @inheritdoc
438
     *
439
     * @throws Error
440
     */
441
    public function offsetExists($offset)
442
    {
443
        throw new Error();
444
    }
445
446
    /**
447
     * @inheritdoc
448
     *
449
     * @throws Error
450
     */
451
    public function offsetUnset($offset)
452
    {
453
        throw new Error();
454
    }
455
}
456