Passed
Push — master ( 8ba67a...0b2f7b )
by ignace nyamagana
01:23
created

Sequence::getIterator()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 2
nc 2
nop 0
dl 0
loc 4
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
45
     */
46
    public function __construct(Period ...$intervals)
47
    {
48
        $this->intervals = $intervals;
49
    }
50
51
    /**
52
     * Returns the sequence boundaries as a Period instance.
53
     *
54
     * If the sequence contains no interval null is returned.
55
     *
56
     * @return ?Period
57
     */
58
    public function getBoundaries(): ?Period
59
    {
60
        $period = reset($this->intervals);
61
        if (false === $period) {
62
            return null;
63
        }
64
65
        return $period->merge(...$this->intervals);
66
    }
67
68
    /**
69
     * Returns the gaps inside the instance.
70
     */
71
    public function getGaps(): self
72
    {
73
        $sequence = new self();
74
        $interval = null;
75
        foreach ($this->sorted([$this, 'sortByStartDate']) as $period) {
76
            if (null === $interval) {
77
                $interval = $period;
78
                continue;
79
            }
80
81
            if (!$interval->overlaps($period) && !$interval->abuts($period)) {
82
                $sequence->push($interval->gap($period));
83
            }
84
85
            if (!$interval->contains($period)) {
86
                $interval = $period;
87
            }
88
        }
89
90
        return $sequence;
91
    }
92
93
    /**
94
     * Sort two Interval instance using their start datepoint.
95
     */
96
    private function sortByStartDate(Period $interval1, Period $interval2): int
97
    {
98
        return $interval1->getStartDate() <=> $interval2->getStartDate();
99
    }
100
101
    /**
102
     * Returns the intersections inside the instance.
103
     */
104
    public function getIntersections(): self
105
    {
106
        $sequence = new self();
107
        $current = null;
108
        $isPreviouslyContained = false;
109
        foreach ($this->sorted([$this, 'sortByStartDate']) as $period) {
110
            if (null === $current) {
111
                $current = $period;
112
                continue;
113
            }
114
115
            $isContained = $current->contains($period);
116
            if ($isContained && $isPreviouslyContained) {
117
                continue;
118
            }
119
            
120
            if ($current->overlaps($period)) {
121
                $sequence->push($current->intersect($period));
122
            }
123
124
            $isPreviouslyContained = $isContained;
125
            if (!$isContained) {
126
                $current = $period;
127
            }
128
        }
129
130
        return $sequence;
131
    }
132
133
    /**
134
     * Tells whether some intervals in the current instance satisfies the predicate.
135
     */
136
    public function some(callable $predicate): bool
137
    {
138
        foreach ($this->intervals as $interval) {
139
            if (true === $predicate($interval)) {
140
                return true;
141
            }
142
        }
143
144
        return false;
145
    }
146
147
    /**
148
     * Tells whether all intervals in the current instance satisfies the predicate.
149
     */
150
    public function every(callable $predicate): bool
151
    {
152
        foreach ($this->intervals as $interval) {
153
            if (true !== $predicate($interval)) {
154
                return false;
155
            }
156
        }
157
158
        return [] !== $this->intervals;
159
    }
160
161
    /**
162
     * Returns the array representation of the sequence.
163
     *
164
     * @return Period[]
165
     */
166
    public function toArray(): array
167
    {
168
        return $this->intervals;
169
    }
170
171
    /**
172
     * {@inheritdoc}
173
     */
174
    public function getIterator(): Iterator
175
    {
176
        foreach ($this->intervals as $offset => $interval) {
177
            yield $offset => $interval;
178
        }
179
    }
180
181
    /**
182
     * {@inheritdoc}
183
     */
184
    public function count(): int
185
    {
186
        return count($this->intervals);
187
    }
188
189
    /**
190
     * Tells whether the sequence is empty.
191
     */
192
    public function isEmpty(): bool
193
    {
194
        return [] === $this->intervals;
195
    }
196
197
    /**
198
     * Tells whether the given interval is present in the sequence.
199
     *
200
     * @param Period ...$intervals
201
     */
202
    public function contains(Period $interval, Period ... $intervals): bool
203
    {
204
        $intervals[] = $interval;
205
        foreach ($intervals as $period) {
206
            if (null === $this->indexOf($period)) {
207
                return false;
208
            }
209
        }
210
211
        return true;
212
    }
213
214
    /**
215
     * Attempts to find the first offset attached to the submitted interval.
216
     *
217
     * If no offset is found the method returns null.
218
     *
219
     * @return ?int
220
     */
221
    public function indexOf(Period $interval): ?int
222
    {
223
        foreach ($this->intervals as $offset => $period) {
224
            if ($period->equals($interval)) {
225
                return $offset;
226
            }
227
        }
228
229
        return null;
230
    }
231
232
    /**
233
     * Returns the interval specified at a given offset.
234
     *
235
     * @throws InvalidIndex If the offset is illegal for the current sequence
236
     */
237
    public function get(int $offset): Period
238
    {
239
        $period = $this->intervals[$offset] ?? null;
240
        if (null !== $period) {
241
            return $period;
242
        }
243
244
        throw new InvalidIndex(sprintf('%s is an invalid offset in the current sequence', $offset));
245
    }
246
247
    /**
248
     * Sort the current instance according to the given comparison callable
249
     * and maintain index association.
250
     *
251
     * Returns true on success or false on failure
252
     */
253
    public function sort(callable $compare): bool
254
    {
255
        return uasort($this->intervals, $compare);
256
    }
257
258
    /**
259
     * Adds new intervals at the front of the sequence.
260
     *
261
     * The sequence is re-indexed after addition
262
     *
263
     * @param Period ...$intervals
264
     */
265
    public function unshift(Period $interval, Period ...$intervals): void
266
    {
267
        $this->intervals = array_merge([$interval], $intervals, $this->intervals);
268
    }
269
270
    /**
271
     * Adds new intervals at the end of the sequence.
272
     *
273
     * @param Period ...$intervals
274
     */
275
    public function push(Period $interval, Period ...$intervals): void
276
    {
277
        $this->intervals = array_merge($this->intervals, [$interval], $intervals);
278
    }
279
280
    /**
281
     * Inserts new intervals at the specified offset of the sequence.
282
     *
283
     * The sequence is re-indexed after addition
284
     * 
285
     * @param Period ...$intervals
286
     *
287
     * @throws InvalidIndex If the offset is illegal for the current sequence.
288
     */
289
    public function insert(int $offset, Period $interval, Period ...$intervals): void
290
    {
291
        if ($offset < 0 || $offset > count($this->intervals)) {
292
            throw new InvalidIndex(sprintf('%s is an invalid offset in the current sequence', $offset));
293
        }
294
295
        array_unshift($intervals, $interval);
296
        array_splice($this->intervals, $offset, 0, $intervals);
297
    }
298
299
    /**
300
     * Updates the interval at the specify offset.
301
     *
302
     * @throws InvalidIndex If the offset is illegal for the current sequence.
303
     */
304
    public function set(int $offset, Period $interval): void
305
    {
306
        $this->get($offset);
307
        $this->intervals[$offset] = $interval;
308
    }
309
310
    /**
311
     * Removes an interval from the sequence at the given offset and returns it.
312
     *
313
     * The sequence is re-indexed after removal
314
     *
315
     * @throws InvalidIndex If the offset is illegal for the current sequence.
316
     */
317
    public function remove(int $offset): Period
318
    {
319
        $interval = $this->get($offset);
320
        unset($this->intervals[$offset]);
321
322
        $this->intervals = array_values($this->intervals);
323
324
        return $interval;
325
    }
326
327
    /**
328
     * Filters the sequence according to the given predicate.
329
     *
330
     * This method MUST retain the state of the current instance, and return
331
     * an instance that contains the interval which validate the predicate.
332
     */
333
    public function filter(callable $predicate): self
334
    {
335
        $intervals = array_filter($this->intervals, $predicate);
336
        if ($intervals === $this->intervals) {
337
            return $this;
338
        }
339
340
        return new self(...$intervals);
341
    }
342
343
    /**
344
     * Removes all intervals from the sequence.
345
     */
346
    public function clear(): void
347
    {
348
        $this->intervals = [];
349
    }
350
351
    /**
352
     * Returns an instance sorted according to the given comparison callable
353
     * but does not maintain index association.
354
     *
355
     * This method MUST retain the state of the current instance, and return
356
     * an instance that contains the sorted intervals. The key are re-indexed
357
     */
358
    public function sorted(callable $compare): self
359
    {
360
        $intervals = $this->intervals;
361
        usort($intervals, $compare);
362
        if ($intervals === $this->intervals) {
363
            return $this;
364
        }
365
366
        return new self(...$intervals);
367
    }
368
}
369