Completed
Push — master ( 9aadcc...35c30e )
by Victor
02:16
created

Flattener::flatten()   C

Complexity

Conditions 12
Paths 18

Size

Total Lines 68
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 33
nc 18
nop 1
dl 0
loc 68
rs 6.9666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Vctls\IntervalGraph;
4
5
use Closure;
6
7
/**
8
 * Transforms an array of overlapping intervals into another array of adjacent intervals.
9
 *
10
 * Each new interval holds the keys of the corresponding original intervals.
11
 *
12
 * @package Vctls\IntervalGraph
13
 */
14
class Flattener
15
{
16
    /** @var Closure Substract one step from a bound value. */
17
    protected $substractStep;
18
19
    /** @var Closure Add one step to the bound value. */
20
    protected $addStep;
21
22
    /**
23
     * Set the Closure for decrementing bound values when dealing with
24
     * discontinuous sets.
25
     *
26
     * @param Closure $substractStep
27
     * @return Flattener
28
     */
29
    public function setSubstractStep(Closure $substractStep): Flattener
30
    {
31
        $this->substractStep = $substractStep;
32
        return $this;
33
    }
34
35
    /**
36
     * Set the Closure for incrementing bound values when dealing with
37
     * discontinuous sets.
38
     *
39
     * @param Closure $addStep
40
     * @return Flattener
41
     */
42
    public function setAddStep(Closure $addStep): Flattener
43
    {
44
        $this->addStep = $addStep;
45
        return $this;
46
    }
47
48
    /**
49
     * Make an array of bounds from an array of intervals.
50
     *
51
     * Assign the value of the interval to each bound.
52
     *
53
     * Assign and a '+' sign if it is a low bound, and a '-' if it is an high bound.
54
     * ```
55
     * bound = [
56
     *   bound value,
57
     *   bound type,
58
     *   TODO included,
59
     *   interval key,
60
     *   interval value
61
     * ]
62
     * ```
63
     *
64
     * @param array[] $intervals
65
     * @return array[]
66
     */
67
    public static function intervalsToSignedBounds($intervals): array
68
    {
69
        $bounds = [];
70
        foreach ($intervals as $key => $interval) {
71
            // TODO Get included boolean from interval bound.
72
            $bounds[] = [$interval[1], '-', true, $key, $interval[2] ?? null];
73
            $bounds[] = [$interval[0], '+', true, $key, $interval[2] ?? null];
74
        }
75
        // Order the bounds.
76
        usort($bounds, static function (array $d1, array $d2) {
77
            return ($d1[0] < $d2[0]) ? -1 : 1;
78
        });
79
        return $bounds;
80
    }
81
82
    /**
83
     * Extract discrete values from an array of intervals.
84
     *
85
     * Intervals with the exact same lower and higher bound will be considered as discrete values.
86
     *
87
     * They will be removed from the initial array, and returned in a separate array.
88
     *
89
     * @param array $intervals The initial array.
90
     * @return array An array containing only discrete values.
91
     */
92
    public static function extractDiscreteValues(array &$intervals): array
93
    {
94
        $discreteValues = array_filter($intervals, static function ($interval) {
95
            return $interval[0] === $interval[1];
96
        });
97
98
        $intervals = array_diff_key($intervals, $discreteValues);
99
100
        return $discreteValues;
101
    }
102
103
    /**
104
     * Create each new interval and calculate its value based on the active intervals on each bound.
105
     *
106
     * @param array[] $intervals
107
     * @return array[]
108
     */
109
    public function flatten(array $intervals): array
110
    {
111
112
        $discreteValues = self::extractDiscreteValues($intervals);
113
114
115
        $bounds = self::intervalsToSignedBounds($intervals);
116
        $newIntervals = [];
117
        $activeIntervals = [];
118
119
        $boundsCount = count($bounds);
120
121
        // Create new intervals for each set of two consecutive bounds,
122
        // and calculate its total value.
123
        for ($i = 1; $i < $boundsCount; $i++) {
124
125
            // Set the current bound.
126
            [$curBoundValue, $curBoundType, $curBoundIncluded, $curBoundIntervalKey] = $bounds[$i - 1];
127
            [$nextBoundValue, $nextBoundType, $nextBoundIncluded] = $bounds[$i];
128
129
            if ($curBoundType === '+') {
130
                // If this is a low bound,
131
                // add the key of the interval to the array of active intervals.
132
                $activeIntervals[$curBoundIntervalKey] = true;
133
            } else {
134
                // If this is an high bound, remove the key.
135
                unset($activeIntervals[$curBoundIntervalKey]);
136
            }
137
138
            if (
139
                isset($this->addStep, $this->substractStep) && (
140
                    ($nextBoundIncluded && $nextBoundType === '+')
141
                    || (!$nextBoundIncluded && $nextBoundType === '+')
142
                )
143
            ) {
144
                $newHighBound = ($this->substractStep)($nextBoundValue);
145
            } else {
146
                $newHighBound = $nextBoundValue;
147
            }
148
149
            if (
150
                isset($this->addStep, $this->substractStep) && $curBoundType === '-' && $curBoundIncluded
151
            ) {
152
                $newLowBound = ($this->addStep)($curBoundValue);
153
            } else {
154
                $newLowBound = $curBoundValue;
155
            }
156
157
            $newIntervals[] = [
158
                $newLowBound,
159
                $newHighBound,
160
                $activeIntervals
161
            ];
162
        }
163
164
        // Remove empty interval generated when two or more intervals share a common bound.
165
        $newIntervals = array_values(array_filter($newIntervals, static function ($i) {
166
            // Use weak comparison in case of object typed bounds.
167
            return $i[0] != $i[1];
168
        }));
169
170
171
        // Push discrete values back into the array.
172
        if (!empty($discreteValues)) {
173
            array_push($newIntervals, ...$discreteValues);
174
        }
175
176
        return $newIntervals;
177
    }
178
}
179