Completed
Push — master ( 53956b...6e2392 )
by Никита
07:19
created

Arrays::generate_all_permutations_iteration()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 25
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 6

Importance

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