Completed
Push — master ( 3032be...02df2a )
by Vitaly
07:51 queued 01:26
created

StructureSorter   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 245
Duplicated Lines 2.45 %

Coupling/Cohesion

Components 2
Dependencies 0

Importance

Changes 0
Metric Value
wmc 32
lcom 2
cbo 0
dl 6
loc 245
rs 9.6
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A sortArrayByKeys() 0 15 2
C getPrefixStructure() 0 42 7
D compareStringStructure() 0 41 9
A equalizeStructures() 0 12 2
A fillMissingStructureGroup() 0 6 2
A compareStructureLengths() 0 21 4
B compareLength() 6 14 5

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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]) {
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...
247
            return ($type === self::G_FIXED ? 1 : -1);
248
        }
249
250 View Code Duplication
        if ($initialGroup[1] < $comparedGroup[1]) {
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...
251
            return ($type === self::G_FIXED ? -1 : 1);
252
        }
253
254
        // Cannot define
255
        return 0;
256
    }
257
}
258