Arrays::select_all_lines_permutations_iteration()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 21
ccs 12
cts 12
cp 1
rs 9.584
c 0
b 0
f 0
cc 3
nc 3
nop 5
crap 3
1
<?php
2
3
    namespace NokitaKaze\OrthogonalArrays;
4
5
    /**
6
     * Class Arrays
7
     * @package NokitaKaze\OrthogonalArrays
8
     */
9
    abstract class Arrays {
10
        const MAXIMUM_VARIANT_PER_ITERATION = 200;
11
12
        /**
13
         * @param integer[] $input
14
         * @param integer   $iteration_size_limit
15
         *
16
         * @return integer[][]
17
         * @throws OrthogonalArraysException
18
         * @throws \InvalidArgumentException
19
         */
20 126
        public static function generateN2(array $input, $iteration_size_limit = self::MAXIMUM_VARIANT_PER_ITERATION) {
21 126
            foreach ($input as $value) {
22 126
                if (!is_int($value)) {
23 42
                    throw new \InvalidArgumentException("Input array must be int[]");
24
                }
25 42
            }
26
            {
27 126
                $input2 = $input;
28 126
                arsort($input2);
29
30 126
                $keys = array_keys($input2);
31 126
                $geometry = array_values($input2);
32 126
                unset($input2);
33
            }
34 126
            $additional_keys = [];
35 126
            while (!empty($geometry)) {
36 126
                $last_count = $geometry[count($geometry) - 1];
37 126
                if ($last_count == 1) {
38 48
                    $additional_keys[] = $keys[count($geometry) - 1];
39 48
                    unset($geometry[count($geometry) - 1]);
40 16
                } else {
41 108
                    break;
42
                }
43 16
            }
44 126
            $additional_keys = array_reverse($additional_keys);
45 126
            $db_value = [];
46 126
            if (count($geometry) == 1) {
47 24
                $db_value = [];
48 24
                for ($i = 0; $i < $geometry[0]; $i++) {
49 24
                    $db_value[] = [$i];
50 8
                }
51 24
                unset($a, $i);
52 110
            } elseif (count($geometry) == 2) {
53 30
                $db_value = [];
54 30
                for ($i = 0; $i < $geometry[0]; $i++) {
55 30
                    for ($j = 0; $j < $geometry[1]; $j++) {
56 30
                        $db_value[] = [$i, $j];
57 10
                    }
58 10
                }
59 30
                unset($a, $i);
60 82
            } elseif (!empty($geometry)) {
61
                /** @noinspection PhpUndefinedClassInspection */
62 54
                $db_value = ArraysDB::get_array($geometry);
63 54
                if (is_null($db_value)) {
64
                    $db_value = self::direct_generateN2($geometry, $iteration_size_limit);
65
                }
66 18
            }
67 126
            $additional_keys_count = count($additional_keys);
68 126
            for ($i = 0; $i < $additional_keys_count; $i++) {
69 48
                if (empty($db_value)) {
70 18
                    $db_value = [[0]];
71 6
                } else {
72 48
                    foreach ($db_value as &$value) {
73 48
                        $value[] = 0;
74 16
                    }
75 48
                    unset($value);
76
                }
77 16
            }
78 126
            unset($additional_keys_count);
79
80 126
            $real_values = null;
81 126
            foreach ($keys as $i => $o) {
82 126
                if ($i !== $o) {
83 20
                    $real_values = array_map(function ($line) use ($keys) {
84 58
                        $new_line = [];
85 58
                        foreach ($keys as $old_value => $new_value) {
86 58
                            $new_line[$new_value] = $line[$old_value];
87 38
                        }
88 58
                        ksort($new_line);
89
90 58
                        return $new_line;
91 58
                    }, $db_value);
92 90
                    break;
93
                }
94 42
            }
95
96 126
            return !is_null($real_values) ? $real_values : $db_value;
97
        }
98
99
        /**
100
         * @param integer[] $geometry
101
         * @param integer   $iteration_size_limit
102
         *
103
         * @return integer[][]
104
         * @throws OrthogonalArraysException
105
         * @doc https://habrahabr.ru/post/187882/
106
         *
107
         * Ведёт себя неоптимально на размере в 2, там оптимально просто перебрать
108
         */
109 96
        protected static function direct_generateN2(array $geometry, $iteration_size_limit) {
110 96
            $full_mutation_left = self::generate_all_permutations_iteration([], $geometry);
111
112 96
            $output = [];
113 96
            while (!empty($full_mutation_left)) {
114 96
                self::direct_generateN2_iteration($full_mutation_left, $output, $iteration_size_limit);
115 32
            }
116
117 64
            usort($output, function ($a, $b) {
118 84
                $a_count = count($a);
119 84
                for ($i = 0; $i < $a_count; $i++) {
120 84
                    if ($a[$i] < $b[$i]) {
121 78
                        return -1;
122 80
                    } elseif ($a[$i] > $b[$i]) {
123 72
                        return 1;
124
                    }
125 26
                }
126
127
                return 0;
128 96
            });
129
130 96
            return $output;
131
        }
132
133
        /**
134
         * @param integer $full_mutation_left_count
135
         * @param integer $iteration_size_limit
136
         *
137
         * @return integer
138
         */
139 96
        protected static function get_max_line_select($full_mutation_left_count, $iteration_size_limit) {
140 96
            $mutation_count = 0;
141 96
            $mutation_count_i = 1;
142 96
            $max_line_select = 1;
143 96
            for ($i = 1; $i < $full_mutation_left_count; $i++) {
144 84
                $mutation_count_i = $mutation_count_i * ($full_mutation_left_count + 1 - $i) / $i;
145 84
                $mutation_count += $mutation_count_i;
146 84
                if ($mutation_count > $iteration_size_limit) {
147 54
                    break;
148
                } else {
149 84
                    $max_line_select = $i;
150
                }
151 28
            }
152
153 96
            return $max_line_select;
154
        }
155
156
        /**
157
         * @param integer[][] $full_mutation_left
158
         * @param integer[][] $output
159
         * @param integer     $iteration_size_limit
160
         *
161
         * @throws OrthogonalArraysException
162
         */
163 96
        protected static function direct_generateN2_iteration(
164
            array &$full_mutation_left,
165
            array &$output,
166
            $iteration_size_limit) {
167
            /**
168
             * @var integer $max_line_select Количество линий, которые добавятся к массиву
169
             */
170 96
            $max_line_select = self::get_max_line_select(count($full_mutation_left), $iteration_size_limit);
171 96
            $max_line_select = max(min($max_line_select, count($full_mutation_left) - 1), 1);
172
173 96
            $sets = [];
174
175 96
            for ($select_line_count = 1; $select_line_count <= $max_line_select; $select_line_count++) {
176 96
                $all_lines = self::select_all_lines_permutations($full_mutation_left, $select_line_count);
177 96
                $best_set = null;
178 96
                $best_left = null;
179
180 96
                foreach ($all_lines as $single_lines_set) {
181 96
                    $temporary = array_merge($output, $single_lines_set);
182 96
                    $full_mutation_left_this = self::remove_useless_lines($temporary, $full_mutation_left);
183 96
                    if (is_null($best_left)) {
184 96
                        $best_set = $single_lines_set;
185 96
                        $best_left = $full_mutation_left_this;
186 88
                    } elseif (count($best_left) + count($best_set) >
187 84
                              count($full_mutation_left_this) + count($single_lines_set)) {
188 48
                        $best_set = $single_lines_set;
189 64
                        $best_left = $full_mutation_left_this;
190 16
                    }
191 32
                }
192
193 96
                $sets[$select_line_count] = [$best_set, $best_left];
194 32
            }
195 96
            unset($best_left, $best_set, $select_line_count, $all_lines,
196 64
                $max_line_select, $temporary, $single_lines_set);
197 96
            usort($sets, function ($set1, $set2) {
198 66
                $v1 = count($set1[0]) + count($set1[1]);
199 66
                $v2 = count($set2[0]) + count($set2[1]);
200 66
                if ($v1 < $v2) {
201 44
                    return -1;
202 66
                } elseif ($v1 > $v2) {
203 48
                    return 1;
204
                } else {
205 36
                    return (count($set1[0]) > count($set1[1])) ? -1 : 1;
206
                }
207 96
            });
208 96
            list($best_set, $best_left) = $sets[array_keys($sets)[0]];
209
210 96
            if (is_null($best_set)) {
211
                // @codeCoverageIgnoreStart
212
                throw new OrthogonalArraysException("Code flow Exception");
213
                // @codeCoverageIgnoreEnd
214
            }
215 96
            foreach ($best_set as $line) {
216 96
                $output[] = $line;
217 32
            }
218 96
            $full_mutation_left = $best_left;
219 96
        }
220
221
        /**
222
         * @param integer[][] $existed
223
         * @param integer[]   $geometry
224
         *
225
         * @return integer[][]
226
         */
227 96
        protected static function generate_all_permutations_iteration(
228
            array $existed,
229
            array $geometry
230
        ) {
231 96
            if (empty($geometry)) {
232 96
                return $existed;
233
            }
234 96
            $index = array_shift($geometry);
235 96
            if (empty($existed)) {
236 96
                for ($i = 0; $i < $index; $i++) {
237 96
                    $existed[] = [$i];
238 32
                }
239 32
            } else {
240 96
                $a = [];
241 96
                for ($i = 0; $i < $index; $i++) {
242 96
                    foreach ($existed as $exist) {
243 96
                        $exist[] = $i;
244 96
                        $a[] = $exist;
245 32
                    }
246 32
                }
247 96
                $existed = $a;
248
            }
249
250 96
            return self::generate_all_permutations_iteration($existed, $geometry);
251
        }
252
253
        /**
254
         * @param array   $lines
255
         * @param integer $select_line_count
256
         *
257
         * @return array[][]
258
         */
259 96
        protected static function select_all_lines_permutations(
260
            $lines,
261
            $select_line_count
262
        ) {
263 96
            $full_exist = [];
264 96
            self::select_all_lines_permutations_iteration($lines, $select_line_count, 0, [], $full_exist);
265
266 96
            return $full_exist;
267
        }
268
269
        /**
270
         * @param array   $lines
271
         * @param integer $select_line_count
272
         * @param integer $min_index
273
         * @param array   $exist
274
         * @param array   $full_exist
275
         */
276 96
        protected static function select_all_lines_permutations_iteration(
277
            $lines,
278
            $select_line_count,
279
            $min_index = 0,
280
            array $exist = [],
281
            array &$full_exist = []
282
        ) {
283 96
            if ($select_line_count == 0) {
284 96
                $full_exist[] = $exist;
285
286 96
                return;
287
            }
288 96
            $lines_count = count($lines);
289 96
            for ($i = $min_index; $i < $lines_count; $i++) {
290 96
                $this_exist = $exist;
291 96
                $this_exist[] = $lines[$i];
292
293 96
                self::select_all_lines_permutations_iteration($lines, $select_line_count - 1,
294 96
                    $i + 1, $this_exist, $full_exist);
295 32
            }
296 96
        }
297
298
        /**
299
         * @param array[] $set
300
         * @param array[] $origin_array
301
         *
302
         * @return array[]
303
         */
304 96
        protected static function remove_useless_lines(array $set, array $origin_array) {
305 96
            if (empty($set)) {
306
                // @codeCoverageIgnoreStart
307
                return $origin_array;
308
                // @codeCoverageIgnoreEnd
309
            }
310 96
            $row_count = count($set[0]);
311 96
            $indexes = [];
312
            // @todo если я захочу делать бесконечную степень свободы, я должен копать отсюда
313 96
            for ($i = 0; $i < $row_count - 1; $i++) {
314 96
                for ($j = $i + 1; $j < $row_count; $j++) {
315 96
                    $indexes[] = [$i, $j];
316 32
                }
317 32
            }
318
319 96
            $existed_pairs = [];
320 96
            foreach ($indexes as $index_id => $index) {
321 96
                $this_set = [];
322 96
                foreach ($set as $value) {
323
                    // @todo бесконечные степени свободы
324 96
                    $this_set[] = [$value[$index[0]], $value[$index[1]]];
325 32
                }
326 96
                $filtered = [];
327
328 96
                $this_set_count = count($this_set);
329 96
                for ($i = 0; $i < $this_set_count; $i++) {
330 96
                    $u = true;
331 96
                    for ($j = 0; $j < $i; $j++) {
332 84
                        if (self::compare_chunk($this_set[$i], $this_set[$j])) {
333 72
                            $u = false;
334 72
                            break;
335
                        }
336 28
                    }
337
338 96
                    if ($u) {
339 96
                        $filtered[] = $this_set[$i];
340 32
                    }
341 32
                }
342
343 96
                $existed_pairs[] = $filtered;
344 32
            }
345
346 96
            $left = [];
347 96
            foreach ($origin_array as $origin_line) {
348 96
                $u = false;
349 96
                foreach ($indexes as $index_id => $index) {
350 96
                    $this_set = [$origin_line[$index[0]], $origin_line[$index[1]]];
351 96
                    $u1 = false;
352 96
                    foreach ($existed_pairs[$index_id] as $in_index_chunk) {
353 96
                        if (self::compare_chunk($in_index_chunk, $this_set)) {
354 96
                            $u1 = true;
355 96
                            break;
356
                        }
357 32
                    }
358
359 96
                    if (!$u1) {
360 84
                        $u = true;
361 88
                        break;
362
                    }
363 32
                }
364
365 96
                if ($u) {
366 88
                    $left[] = $origin_line;
367 28
                }
368 32
            }
369
370 96
            return $left;
371
        }
372
373
        /**
374
         * @param array $a
375
         * @param array $b
376
         *
377
         * @return boolean
378
         */
379 96
        protected static function compare_chunk(array $a, array $b) {
380 96
            foreach ($a as $num => $value) {
381 96
                if (!self::compare_value($value, $b[$num])) {
382 88
                    return false;
383
                }
384 32
            }
385
386 96
            return true;
387
        }
388
389
        /**
390
         * @param mixed $a
391
         * @param mixed $b
392
         *
393
         * @return boolean
394
         */
395 156
        protected static function compare_value($a, $b) {
396 156
            if (is_null($a)) {
397 24
                return is_null($b);
398 156
            } elseif (is_null($b)) {
399 6
                return is_null($a);
400
            } else {
401 156
                return ($a === $b);
402
            }
403
        }
404
405
        /**
406
         * @param array[] $values
407
         * @param integer $iteration_size_limit
408
         *
409
         * @return array[]
410
         * @throws OrthogonalArraysException
411
         * @throws \InvalidArgumentException
412
         */
413 24
        public static function generateN2_values(array $values, $iteration_size_limit = self::MAXIMUM_VARIANT_PER_ITERATION) {
414 24
            $geometry = [];
415 24
            foreach ($values as $value) {
416 24
                $geometry[] = count($value);
417 8
            }
418
419 24
            $output_raw = self::generateN2($geometry, $iteration_size_limit);
420 24
            $output = [];
421 24
            foreach ($output_raw as $line) {
422 24
                $output_line = [];
423 24
                foreach ($line as $i => $index) {
424 24
                    $output_line[] = $values[$i][$index];
425 8
                }
426
427 24
                $output[] = $output_line;
428 8
            }
429
430 24
            return $output;
431
        }
432
433
        /**
434
         * @param array[] $values
435
         * @param integer $iteration_size_limit
436
         *
437
         * @return array[]
438
         * @throws OrthogonalArraysException
439
         * @throws \InvalidArgumentException
440
         */
441 24
        public static function squeeze(array $values, $iteration_size_limit = self::MAXIMUM_VARIANT_PER_ITERATION) {
442 24
            $unique_values = self::get_unique_values($values);
443
444 24
            return self::generateN2_values($unique_values, $iteration_size_limit);
445
        }
446
447 60
        protected static function get_unique_values_item(array $input) {
448 60
            $filtered = [];
449 60
            foreach ($input as $value) {
450 60
                $u = false;
451 60
                foreach ($filtered as $filtered_value) {
452 60
                    if (self::compare_value($value, $filtered_value)) {
453 48
                        $u = true;
454 52
                        break;
455
                    }
456 20
                }
457
458 60
                if (!$u) {
459 60
                    $filtered[] = $value;
460 20
                }
461 20
            }
462
463 60
            return $filtered;
464
        }
465
466 24
        protected static function get_unique_values(array $values) {
467 24
            $unique_values = [];
468 24
            $line_width = count($values[0]);
469 24
            for ($i = 0; $i < $line_width; $i++) {
470 24
                $unique_values[] = [];
471 8
            }
472 24
            foreach ($values as $value) {
473 24
                foreach ($value as $index => $sub_value) {
474 24
                    $unique_values[$index][] = $sub_value;
475 8
                }
476 8
            }
477 24
            foreach ($unique_values as &$unique_sub_values) {
478 24
                $unique_sub_values = self::get_unique_values_item($unique_sub_values);
479 8
            }
480
481 24
            return $unique_values;
482
        }
483
    }
484
485
?>