Completed
Push — master ( e2d05f...edeffb )
by Vitaly
09:12
created

StructureSorter::sortArrayByKeys()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 35
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 35
ccs 12
cts 12
cp 1
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 17
nc 4
nop 1
crap 4
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
use samsonframework\stringconditiontree\string\Structure;
9
10
/**
11
 * Parametrized strings sorting.
12
 *
13
 * @author Vitaly Egorov <[email protected]>
14
 */
15
class StructureSorter
16
{
17
    /** Variable length characters group */
18
    const G_VARIABLE = 0;
19
20
    /** Fixed length characters group */
21
    const G_FIXED = 1;
22
23
    /** @var string Parametrized string start marker */
24
    protected $parameterStartMarker;
25
26
    /** @var string Parametrized string end marker */
27
    protected $parameterEndMarker;
28
29
    /**
30
     * StructureSorter constructor.
31
     *
32
     * @param string $parameterStartMarker Parametrized string start marker
33
     * @param string $parameterEndMarker Parametrized string end marker
34
     */
35 1
    public function __construct(string $parameterStartMarker, string $parameterEndMarker)
36
    {
37 1
        $this->parameterStartMarker = $parameterStartMarker;
38 1
        $this->parameterEndMarker = $parameterEndMarker;
39 1
    }
40
41
    /**
42
     * Sort strings array considering PCG and NPCG string structure.
43
     *
44
     * @param array $input Input array for sorting
45
     *
46
     * @return array Sorted keys array
47
     */
48 1
    public function sortArrayByKeys(array $input): array
49
    {
50
        /** @var Structure[] $structures */
51 1
        $structures = [];
52 1
        foreach (array_keys($input) as $string) {
53 1
            $structures[$string] = new Structure($string);
54
        }
55
56 1
        usort($structures, function(Structure $initial, Structure $compared) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
57 1
            return $initial->compare($compared);
58 1
        });
59
60 1
        $structures = array_reverse($structures);
61
62
        // Restore initial strings sub-arrays
63 1
        $result = [];
64 1
        foreach ($structures as $structure) {
65 1
            $result[$structure->input] = $input[$structure->input];
66
        }
67 1
        return $result;
68
69
70
        // Convert string array keys into structure arrays
71
        $prefixes = array_map([$this, 'getPrefixStructure'], array_keys($input));
0 ignored issues
show
Unused Code introduced by
// Convert string array ...), array_keys($input)); does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
72
73
        // Sort parametrized string array according sorting rules
74
        usort($prefixes, [$this, 'compareStringStructure']);
75
76
        // Restore initial strings sub-arrays
77
        $result = [];
78
        foreach ($prefixes as $sortingData) {
79
            $result[$sortingData[0][2]] = $input[$sortingData[0][2]];
80
        }
81
        return $result;
82
    }
83
84
    /**
85
     * Build string character group structure considering parametrized
86
     * and not parametrized character groups and their length(PCG, NPCG).
87
     *
88
     * @param string $prefix Prefix string
89
     *
90
     * @return array String character groups structure
91
     */
92
    protected function getPrefixStructure(string $prefix): array
93
    {
94
        /** @var array $structureMatrix String PCG(0)/NPCG(1) structure matrix for comparison */
95
        $structureMatrix = [];
96
97
        // Flags for showing current string character group
98
        /** @var bool $isPCG Flags showing PCG started */
99
        $isPCG = false;
100
        /** @var bool $isNPCG Flags showing NPCG started */
101
        $isNPCG = true;
102
103
        // Pointer to current CG to count string NPCG length
104
        $currentCG = 0;
105
106
        /**
107
         * TODO: Try to find PCG filter :... pattern and process it also as
108
         * PCG with filters should be prioritized over PSG without filter
109
         * even if filter is .*
110
         */
111
112
        // Iterate string by characters
113
        for ($i = 0, $length = strlen($prefix); $i < $length; $i++) {
114
            if (!$isPCG && $prefix{$i} === $this->parameterStartMarker) {
115
                $isPCG = true;
116
                $isNPCG = false;
117
                $structureMatrix[] = [0, 0, $prefix];
118
                $currentCG = &$structureMatrix[count($structureMatrix) - 1][1];
119
            } elseif ($isPCG && $prefix{$i} === $this->parameterEndMarker) {
120
                $isPCG = false;
121
                $isNPCG = true;
122
            } elseif ($isNPCG) {
123
                $isNPCG = false;
124
                $structureMatrix[] = [1, 0, $prefix];
125
                $currentCG = &$structureMatrix[count($structureMatrix) - 1][1];
126
            }
127
128
            // Store current character group length
129
            $currentCG++;
130
        }
131
132
        return $structureMatrix;
133
    }
134
135
    /**
136
     * Compare string structures.
137
     *
138
     * @param array $initial  Initial string structure
139
     * @param array $compared Compared string structure
140
     *
141
     * @return int Result of array elements comparison
142
     */
143
    protected function compareStringStructure(array $initial, array $compared): int
144
    {
145
        $this->equalizeStructures($initial, $compared);
146
147
        /**
148
         * If all CG in structure are fixed then shortest should have higher priority
149
         * If first CG is variable and second is fixed then longest fixed should have higher priority.
150
         */
151
152
        // Iterate every CGS
153
        foreach ($initial as $key => $initialGroup) {
154
            $comparedGroup = $compared[$key];
155
156
            /**
157
             * FCG has higher priority then VCG.
158
             * Compare CG type
159
             */
160
            $return = $this->compareCSGData($initialGroup, $comparedGroup, self::G_VARIABLE, 0);
161
162
            // If CG are equal and are fixed type
163
            if ($key > 0 && $return === 0 && $initialGroup[0] === self::G_FIXED) {
164
                /**
165
                 * Compare fixed CG lengths. Fixed CG longer
166
                 * should have higher priority.
167
                 */
168
                $return = $this->compareCSGData($initialGroup, $comparedGroup, self::G_VARIABLE, 1);
169
            }
170
171
            if ($return !== 0) {
172
                return $return;
173
            }
174
175
            // They are equal continue to next structure group comparison
176
        }
177
178
        // Compare fixed length CGS
179
        $return = $this->compareStructureLengths($initial, $compared, self::G_FIXED);
180
181
        // Fixed CGS are equal
182
        if ($return === 0) {
183
            // Compare variable length CGS
184
            $return = $this->compareStructureLengths($initial, $compared, self::G_VARIABLE);
185
        }
186
187
        return $return;
188
    }
189
190
    /**
191
     * Make CGS equals size.
192
     *
193
     * @param array $initial Initial CGS, will be changed
194
     * @param array $compared Compared CGS, will be changed
195
     *
196
     * @return int Longest CGS size(now they are both equal)
197
     */
198
    protected function equalizeStructures(array &$initial, array &$compared): int
199
    {
200
        $size = max(count($initial), count($compared));
201
202
        // Make structures same size preserving previous existing structure value
203
        for ($i = 1; $i < $size; $i++) {
204
            $this->fillMissingStructureGroup($initial, $i);
205
            $this->fillMissingStructureGroup($compared, $i);
206
        }
207
208
        return $size;
209
    }
210
211
    /**
212
     * Fill CSG with previous group value if not present.
213
     *
214
     * @param array $groups CSG for filling
215
     * @param int   $index  CSG index
216
     */
217
    private function fillMissingStructureGroup(array &$groups, int $index)
218
    {
219
        if (!array_key_exists($index, $groups)) {
220
            $groups[$index] = $groups[$index - 1];
221
        }
222
    }
223
224
    /**
225
     * Compare longer CGS considering that:
226
     * - Shortest fixed CGS should have higher priority
227
     * - Longest variable CGS should have higher priority
228
     *
229
     * @param array $initialGroup  Initial CGS
230
     * @param array $comparedGroup Compared CGS
231
     * @param int   $type          Fixed/Variable CGS
232
     * @param int   $dataIndex CSG creationData index
233
     *
234
     * @return int 0 if initial CGS is not longer than compared,
235
     *                  otherwise -1/1 depending on CGS type.
236
     */
237
    private function compareCSGData(array $initialGroup, array $comparedGroup, int $type, int $dataIndex = 1): int
238
    {
239
        // Compare character group length
240 View Code Duplication
        if ($initialGroup[$dataIndex] > $comparedGroup[$dataIndex]) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
241
            return ($type === self::G_FIXED ? 1 : -1);
242
        }
243
244 View Code Duplication
        if ($initialGroup[$dataIndex] < $comparedGroup[$dataIndex]) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
245
            return ($type === self::G_FIXED ? -1 : 1);
246
        }
247
248
        // Cannot define
249
        return 0;
250
    }
251
252
    /**
253
     * Compare two character group structure(CGS) length and define
254
     * which one is longer.
255
     *
256
     * @param array $initial Initial CGS
257
     * @param array $compared Compared CGS
258
     * @param int   $type CGS type (Variable|Fixed length)
259
     *
260
     * @return int -1 if initial CGS longer
261
     *             0 if initial and compared CGS are equal
262
     *             1 if compared CGS longer
263
     */
264
    protected function compareStructureLengths(array $initial, array $compared, int $type = self::G_FIXED): int
265
    {
266
        // Iterate character group structures
267
        foreach ($initial as $index => $initialGroup) {
268
            $comparedGroup = $compared[$index];
269
            // Check if character group matches passed character group type
270
            if ($initialGroup[0] === $type) {
271
                $return = $this->compareCSGData($initialGroup, $comparedGroup, $type);
272
273
                // Compare character group length
274
                if ($return !== 0) {
275
                    return $return;
276
                }
277
278
                // Continue to next CGS
279
            }
280
        }
281
282
        // CGS have equal length
283
        return 0;
284
    }
285
}
286