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
|
|
|
|
218
|
|
|
// Compare character group length |
219
|
|
|
if ($return !== 0) { |
220
|
|
|
return $return; |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
// Continue to next CGS |
224
|
|
|
} |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
// CGS have equal length |
228
|
|
|
return 0; |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
/** |
232
|
|
|
* Compare longer CGS considering that: |
233
|
|
|
* - Shortest fixed CGS should have higher priority |
234
|
|
|
* - Longest variable CGS should have higher priority |
235
|
|
|
* |
236
|
|
|
* @param array $initialGroup Initial CGS |
237
|
|
|
* @param array $comparedGroup Compared CGS |
238
|
|
|
* @param int $type Fixed/Variable CGS |
239
|
|
|
* |
240
|
|
|
* @return int 0 if initial CGS is not longer than compared, |
241
|
|
|
* otherwise -1/1 depending on CGS type. |
242
|
|
|
*/ |
243
|
|
|
private function compareLength(array $initialGroup, array $comparedGroup, int $type) |
244
|
|
|
{ |
245
|
|
|
// Compare character group length |
246
|
|
View Code Duplication |
if ($initialGroup[1] > $comparedGroup[1]) { |
|
|
|
|
247
|
|
|
return ($type === self::G_FIXED ? 1 : -1); |
248
|
|
|
} |
249
|
|
|
|
250
|
|
View Code Duplication |
if ($initialGroup[1] < $comparedGroup[1]) { |
|
|
|
|
251
|
|
|
return ($type === self::G_FIXED ? -1 : 1); |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
// Cannot define |
255
|
|
|
return 0; |
256
|
|
|
} |
257
|
|
|
} |
258
|
|
|
|
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.