Completed
Push — master ( 9c4bd4...6c198d )
by ignace nyamagana
05:59 queued 04:30
created

Sequence::unshift()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * League.Period (https://period.thephpleague.com).
5
 *
6
 * (c) Ignace Nyamagana Butera <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace League\Period;
15
16
use Countable;
17
use Iterator;
18
use IteratorAggregate;
19
use function array_filter;
20
use function array_merge;
21
use function count;
22
use function reset;
23
use function sort;
24
use function sprintf;
25
use function uasort;
26
27
/**
28
 * A class to manipulate interval collection.
29
 *
30
 * @package League.period
31
 * @author  Ignace Nyamagana Butera <[email protected]>
32
 * @since   4.1.0
33
 */
34
final class Sequence implements Countable, IteratorAggregate
35
{
36
    /**
37
     * @var Period[]
38
     */
39
    private $intervals = [];
40
41
    /**
42
     * new instance.
43
     *
44
     * @param Period... $intervals
0 ignored issues
show
Documentation Bug introduced by
The doc comment Period... at position 0 could not be parsed: Unknown type name 'Period...' at position 0 in Period....
Loading history...
45
     */
46
    public function __construct(Period ...$intervals)
47
    {
48
        $this->intervals = $intervals;
49
    }
50
51
    /**
52
     * {@inheritdoc}
53
     */
54
    public function count(): int
55
    {
56
        return count($this->intervals);
57
    }
58
59
    /**
60
     * {@inheritdoc}
61
     */
62
    public function getIterator(): Iterator
63
    {
64
        foreach ($this->intervals as $offset => $interval) {
65
            yield $offset => $interval;
66
        }
67
    }
68
69
    /**
70
     * Tells whether the sequence is empty.
71
     */
72
    public function isEmpty(): bool
73
    {
74
        return [] === $this->intervals;
75
    }
76
77
    /**
78
     * Removes all intervals from the sequence.
79
     */
80
    public function clear(): void
81
    {
82
        $this->intervals = [];
83
    }
84
85
    /**
86
     * Returns the array representation of the sequence.
87
     *
88
     * @return Period[]
89
     */
90
    public function toArray(): array
91
    {
92
        return $this->intervals;
93
    }
94
95
    /**
96
     * Returns the interval specified at a given offset.
97
     *
98
     * @throws InvalidIndex If the offset is illegal for the current sequence
99
     */
100
    public function get(int $offset): Period
101
    {
102
        $period = $this->intervals[$offset] ?? null;
103
        if (null !== $period) {
104
            return $period;
105
        }
106
107
        throw new InvalidIndex(sprintf('%s is an invalid offset in the current sequence', $offset));
108
    }
109
110
    /**
111
     * Removes an interval from the sequence at the given offset and returns it.
112
     *
113
     * The sequence is re-indexed after removal
114
     *
115
     * @throws InvalidIndex If the offset is illegal for the current sequence.
116
     */
117
    public function remove(int $offset): Period
118
    {
119
        $interval = $this->get($offset);
120
        unset($this->intervals[$offset]);
121
122
        $this->intervals = array_values($this->intervals);
123
124
        return $interval;
125
    }
126
127
    /**
128
     * Adds new interval at the front of the sequence.
129
     *
130
     * The sequence is re-indexed after addition
131
     *
132
     * @param Period... $intervals
0 ignored issues
show
Documentation Bug introduced by
The doc comment Period... at position 0 could not be parsed: Unknown type name 'Period...' at position 0 in Period....
Loading history...
133
     */
134
    public function unshift(Period $interval, Period ...$intervals): void
135
    {
136
        $this->intervals = array_merge([$interval], $intervals, $this->intervals);
137
    }
138
139
    /**
140
     * Adds new interval at the end of the sequence.
141
     *
142
     * @param Period... $intervals
0 ignored issues
show
Documentation Bug introduced by
The doc comment Period... at position 0 could not be parsed: Unknown type name 'Period...' at position 0 in Period....
Loading history...
143
     */
144
    public function push(Period $interval, Period ...$intervals): void
145
    {
146
        $this->intervals = array_merge($this->intervals, [$interval], $intervals);
147
    }
148
149
    /**
150
     * Updates the interval at the specify offset.
151
     *
152
     * @throws InvalidIndex If the offset is illegal for the current sequence.
153
     */
154
    public function set(int $offset, Period $interval): void
155
    {
156
        $this->get($offset);
157
        $this->intervals[$offset] = $interval;
158
    }
159
160
    /**
161
     * Tells whether the given interval is present in the sequence.
162
     * @param Period... $intervals
0 ignored issues
show
Documentation Bug introduced by
The doc comment Period... at position 0 could not be parsed: Unknown type name 'Period...' at position 0 in Period....
Loading history...
163
     */
164
    public function contains(Period $interval, Period ... $intervals): bool
165
    {
166
        $intervals[] = $interval;
167
        foreach ($intervals as $period) {
168
            if (null === $this->indexOf($period)) {
169
                return false;
170
            }
171
        }
172
173
        return true;
174
    }
175
176
    /**
177
     * Attempts to find the first offset attached to the submitted interval.
178
     *
179
     * If no offset is found the method returns null.
180
     *
181
     * @return ?int
182
     */
183
    public function indexOf(Period $interval): ?int
184
    {
185
        foreach ($this->intervals as $offset => $period) {
186
            if ($period->equals($interval)) {
187
                return $offset;
188
            }
189
        }
190
191
        return null;
192
    }
193
194
    /**
195
     * Returns the sequence boundaries as a Period instance.
196
     *
197
     * If the sequence contains no interval null is returned.
198
     *
199
     * @return ?Period
200
     */
201
    public function getBoundaries(): ?Period
202
    {
203
        $period = reset($this->intervals);
204
        if (false === $period) {
205
            return null;
206
        }
207
208
        return $period->merge(...$this->intervals);
209
    }
210
211
    /**
212
     * Sort the current instance according to the given comparison callable
213
     * and maintain index association.
214
     *
215
     * Returns true on success or false on failure
216
     */
217
    public function sort(callable $compare): bool
218
    {
219
        return uasort($this->intervals, $compare);
220
    }
221
222
    /**
223
     * Returns an instance sorted according to the given comparison callable
224
     * but does not maintain index association.
225
     *
226
     * This method MUST retain the state of the current instance, and return
227
     * an instance that contains the sorted intervals. The key are re-indexed
228
     */
229
    public function sorted(callable $compare): self
230
    {
231
        $intervals = $this->intervals;
232
        usort($intervals, $compare);
233
        if ($intervals === $this->intervals) {
234
            return $this;
235
        }
236
237
        return new self(...$intervals);
238
    }
239
240
    /**
241
     * Filters the sequence according to the given predicate.
242
     *
243
     * This method MUST retain the state of the current instance, and return
244
     * an instance that contains the interval which validate the predicate.
245
     */
246
    public function filter(callable $predicate): self
247
    {
248
        $intervals = array_filter($this->intervals, $predicate);
249
        if ($intervals === $this->intervals) {
250
            return $this;
251
        }
252
253
        return new self(...$intervals);
254
    }
255
256
    /**
257
     * Tells whether some intervals in the current instance satisfies the predicate.
258
     */
259
    public function some(callable $predicate): bool
260
    {
261
        foreach ($this->intervals as $interval) {
262
            if (true === $predicate($interval)) {
263
                return true;
264
            }
265
        }
266
267
        return false;
268
    }
269
270
    /**
271
     * Tells whether all intervals in the current instance satisfies the predicate.
272
     */
273
    public function every(callable $predicate): bool
274
    {
275
        foreach ($this->intervals as $interval) {
276
            if (true !== $predicate($interval)) {
277
                return false;
278
            }
279
        }
280
281
        return [] !== $this->intervals;
282
    }
283
284
    /**
285
     * Returns the gaps inside the instance.
286
     */
287
    public function getGaps(): self
288
    {
289
        $sequence = new self();
290
        $interval = null;
291
        $currentInterval = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $currentInterval is dead and can be removed.
Loading history...
292
        foreach ($this->sorted([$this, 'sortByStartDate']) as $period) {
293
            $currentInterval = $period;
294
            if (null === $interval) {
295
                $interval = $currentInterval;
296
                continue;
297
            }
298
299
            if (!$interval->overlaps($currentInterval) && !$interval->abuts($currentInterval)) {
300
                $sequence->push($interval->gap($currentInterval));
301
            }
302
303
            if (!$interval->contains($currentInterval)) {
304
                $interval = $currentInterval;
305
            }
306
        }
307
308
        return $sequence;
309
    }
310
311
    /**
312
     * Sort two Interval instance using their start datepoint.
313
     */
314
    private function sortByStartDate(Period $interval1, Period $interval2): int
315
    {
316
        return $interval1->getStartDate() <=> $interval2->getStartDate();
317
    }
318
319
    /**
320
     * Returns the intersections inside the instance.
321
     */
322
    public function getIntersections(): self
323
    {
324
        $sequence = new self();
325
        $interval = null;
326
        $currentInterval = null;
327
        foreach ($this->sorted([$this, 'sortByStartDate']) as $period) {
328
            if (null === $interval) {
329
                $interval = $period;
330
                continue;
331
            }
332
333
            if (null !== $currentInterval && $interval->contains($period)) {
334
                continue;
335
            }
336
337
            $currentInterval = $period;
338
            if ($interval->overlaps($currentInterval)) {
339
                $sequence->push($interval->intersect($currentInterval));
340
            }
341
342
            if (!$interval->contains($currentInterval)) {
343
                $interval = $currentInterval;
344
                $currentInterval = null;
345
            }
346
        }
347
348
        return $sequence;
349
    }
350
}
351