Completed
Push — master ( 7805c7...3032be )
by Vitaly
14:07 queued 01:03
created

StructureSorter::compareLength()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 4
nc 3
nop 3
1
<?php declare(strict_types=1);
2
/**
3
 * Created by Vitaly Iegorov <[email protected]>.
4
 * on 05.04.17 at 15:18
5
 */
6
namespace samsonframework\stringconditiontree;
7
8
/**
9
 * Parametrized strings sorting.
10
 *
11
 * @author Vitaly Egorov <[email protected]>
12
 */
13
class StructureSorter
14
{
15
    /** Variable length characters group */
16
    const G_VARIABLE = 0;
17
18
    /** Fixed length characters group */
19
    const G_FIXED = 1;
20
21
    /** @var string Parametrized string start marker */
22
    protected $parameterStartMarker;
23
24
    /** @var string Parametrized string end marker */
25
    protected $parameterEndMarker;
26
27
    /**
28
     * StructureSorter constructor.
29
     *
30
     * @param string $parameterStartMarker Parametrized string start marker
31
     * @param string $parameterEndMarker Parametrized string end marker
32
     */
33
    public function __construct(string $parameterStartMarker, string $parameterEndMarker)
34
    {
35
        $this->parameterStartMarker = $parameterStartMarker;
36
        $this->parameterEndMarker = $parameterEndMarker;
37
    }
38
39
    /**
40
     * Sort strings array considering PCG and NPCG string structure.
41
     *
42
     * @param array $input Input array for sorting
43
     *
44
     * @return array Sorted keys array
45
     */
46
    public function sortArrayByKeys(array $input): array
47
    {
48
        // Convert string array keys into structure arrays
49
        $prefixes = array_map([$this, 'getPrefixStructure'], array_keys($input));
50
51
        // Sort parametrized string array according sorting rules
52
        usort($prefixes, [$this, 'compareStringStructure']);
53
54
        // Restore initial strings sub-arrays
55
        $result = [];
56
        foreach ($prefixes as $sortingData) {
57
            $result[$sortingData[0][2]] = $input[$sortingData[0][2]];
58
        }
59
        return $result;
60
    }
61
62
    /**
63
     * Build string character group structure considering parametrized
64
     * and not parametrized character groups and their length(PCG, NPCG).
65
     *
66
     * @param string $prefix Prefix string
67
     *
68
     * @return array String character groups structure
69
     */
70
    protected function getPrefixStructure(string $prefix): array
71
    {
72
        /** @var array $structureMatrix String PCG(0)/NPCG(1) structure matrix for comparison */
73
        $structureMatrix = [];
74
75
        // Flags for showing current string character group
76
        /** @var bool $isPCG Flags showing PCG started */
77
        $isPCG = false;
78
        /** @var bool $isNPCG Flags showing NPCG started */
79
        $isNPCG = true;
80
81
        // Pointer to current CG to count string NPCG length
82
        $currentCG = 0;
83
84
        /**
85
         * TODO: Try to find PCG filter :... pattern and process it also as
86
         * PCG with filters should be prioritized over PSG without filter
87
         * even if filter is .*
88
         */
89
90
        // Iterate string by characters
91
        for ($i = 0, $length = strlen($prefix); $i < $length; $i++) {
92
            if (!$isPCG && $prefix{$i} === $this->parameterStartMarker) {
93
                $isPCG = true;
94
                $isNPCG = false;
95
                $structureMatrix[] = [0, 0, $prefix];
96
                $currentCG = &$structureMatrix[count($structureMatrix) - 1][1];
97
            } elseif ($isPCG && $prefix{$i} === $this->parameterEndMarker) {
98
                $isPCG = false;
99
                $isNPCG = true;
100
            } elseif ($isNPCG) {
101
                $isNPCG = false;
102
                $structureMatrix[] = [1, 0, $prefix];
103
                $currentCG = &$structureMatrix[count($structureMatrix) - 1][1];
104
            }
105
106
            // Store current character group length
107
            $currentCG++;
108
        }
109
110
        return $structureMatrix;
111
    }
112
113
    /**
114
     * Compare string structures.
115
     *
116
     * @param array $initial  Initial string structure
117
     * @param array $compared Compared string structure
118
     *
119
     * @return int Result of array elements comparison
120
     */
121
    protected function compareStringStructure(array $initial, array $compared): int
122
    {
123
        $maxStructureSize = $this->equalizeStructures($initial, $compared);
124
125
        // Iterate every structure group
126
        for ($i = 0; $i < $maxStructureSize; $i++) {
127
            // If initial structure has NPCG than it has higher priority
128
            if ($initial[$i][0] > $compared[$i][0]) {
129
                return -1;
130
            }
131
132
            // If compared structure has NPCG than it has higher priority
133
            if ($initial[$i][0] < $compared[$i][0]) {
134
                return 1;
135
            }
136
137
            // Compare NOT starting NPCG length
138
            if ($i > 0 && $initial[$i][0] === 1) {
139
                if ($initial[$i][1] > $compared[$i][1]) {
140
                    return -1;
141
                }
142
143
                if ($initial[$i][1] < $compared[$i][1]) {
144
                    return 1;
145
                }
146
            }
147
148
            // They are equal continue to next structure group comparison
149
        }
150
151
        // Compare fixed length CGS
152
        $return = $this->compareStructureLengths($initial, $compared, self::G_FIXED);
153
154
        // Fixed CGS are equal
155
        if ($return === 0) {
156
            // Compare variable length CGS
157
            $return = $this->compareStructureLengths($initial, $compared, self::G_VARIABLE);
158
        }
159
160
        return $return;
161
    }
162
163
    /**
164
     * Make CGS equals size.
165
     *
166
     * @param array $initial Initial CGS, will be changed
167
     * @param array $compared Compared CGS, will be changed
168
     *
169
     * @return int Longest CGS size(now they are both equal)
170
     */
171
    protected function equalizeStructures(array &$initial, array &$compared): int
172
    {
173
        $size = max(count($initial), count($compared));
174
175
        // Make structures same size preserving previous existing structure value
176
        for ($i = 1; $i < $size; $i++) {
177
            $this->fillMissingStructureGroup($initial, $i);
178
            $this->fillMissingStructureGroup($compared, $i);
179
        }
180
181
        return $size;
182
    }
183
184
    /**
185
     * Fill CSG with previous group value if not present.
186
     *
187
     * @param array $groups CSG for filling
188
     * @param int   $index  CSG index
189
     */
190
    private function fillMissingStructureGroup(array &$groups, int $index)
191
    {
192
        if (!array_key_exists($index, $groups)) {
193
            $groups[$index] = $groups[$index - 1];
194
        }
195
    }
196
197
    /**
198
     * Compare two character group structure(CGS) length and define
199
     * which one is longer.
200
     *
201
     * @param array $initial Initial CGS
202
     * @param array $compared Compared CGS
203
     * @param int   $type CGS type (Variable|Fixed length)
204
     *
205
     * @return int -1 if initial CGS longer
206
     *             0 if initial and compared CGS are equal
207
     *             1 if compared CGS longer
208
     */
209
    protected function compareStructureLengths(array $initial, array $compared, int $type = self::G_FIXED): int
210
    {
211
        // Iterate character group structures
212
        foreach ($initial as $index => $initialGroup) {
213
            $comparedGroup = $compared[$index];
214
            // Check if character group matches passed character group type
215
            if ($initialGroup[0] === $type) {
216
                $return = $this->compareLength($initialGroup, $comparedGroup, $type)
217
                ?? $this->compareLength($initialGroup, $comparedGroup, $type)
218
                ?? 0;
219
220
                // Compare character group length
221
                if ($return !== 0) {
222
                    return $return;
223
                }
224
225
                // Continue to next CGS
226
            }
227
        }
228
229
        // CGS have equal length
230
        return 0;
231
    }
232
233
    /**
234
     * Compare longer CGS considering that:
235
     * - Shortest fixed CGS should have higher priority
236
     * - Longest variable CGS should have higher priority
237
     *
238
     * @param array $initialGroup Initial CGS
239
     * @param array $comparedGroup Compared CGS
240
     * @param int   $type Fixed/Variable CGS
241
     *
242
     * @return int|null Null if initial CGS is not longer than compared,
243
     *                  otherwise -1/1 depending on CGS type.
244
     */
245
    private function compareLength(array $initialGroup, array $comparedGroup, int $type)
246
    {
247
        // Compare character group length
248
        if ($initialGroup[1] > $comparedGroup[1]) {
249
            return ($type === self::G_FIXED ? 1 : -1);
250
        }
251
252
        // Cannot define
253
        return null;
254
    }
255
}
256