Рекомендации по производительности для поиска уникальной перестановки

TLDR: как найти многомерную перестановку массивов в php и как оптимизировать для больших массивов?

Это продолжение этого вопроса: как найти многомерную перестановку массивов в php

у нас есть скрипт для сортировки массива, идея состоит в том, чтобы найти уникальную перестановку массива, правила для поиска этой перестановки:

  1. Входной массив содержит набор массивов.
  2. Каждый внутренний массив содержит уникальные элементы.
  3. Каждый внутренний массив может иметь разную длину и разные значения.
  4. Выходной массив должен содержать одни и те же значения.
  5. Внутренний массив вывода должен иметь уникальные значения для одного ключа.
  6. Если нет решения, допустимы подстановочные знаки ie.: null .
  7. Подстановочные знаки можно дублировать на одном и том же ключе.
  8. Решение должно иметь как можно меньше подстановочных знаков.
  9. Алгоритм должен иметь возможность обрабатывать массив до 30×30 менее чем за 180 секунд.

У меня есть это решение:

 function matrix_is_solved(array $matrix) { foreach (array_keys(current($matrix)) as $offset) { $column = array_filter($raw = array_column($matrix, $offset)); if (count($column) != count(array_unique($column))) return false; } return true; } function matrix_generate_vectors(array $matrix) { $vectors = []; $columns = count(current($matrix)); $gen = function ($depth=0, $combo='') use (&$gen, &$vectors, $columns) { if ($depth < $columns) for ($i = 0; $i < $columns; $i++) $gen($depth + 1, $i . $combo); else $vectors[] = array_map('intval', str_split($combo)); }; $gen(); return $vectors; } function matrix_rotate(array $matrix, array $vector) { foreach ($matrix as $row => &$values) { array_rotate($values, $vector[$row]); } return $matrix; } function matrix_brute_solve(array $matrix) { matrix_make_square($matrix); foreach (matrix_generate_vectors($matrix) as $vector) { $attempt = matrix_rotate($matrix, $vector); if (matrix_is_solved($attempt)) return matrix_display($attempt); } echo 'No solution'; } function array_rotate(array &$array, $offset) { foreach (array_slice($array, 0, $offset) as $key => $val) { unset($array[$key]); $array[$key] = $val; } $array = array_values($array); } function matrix_display(array $matrix = null) { echo "[\n"; foreach ($matrix as $row => $inner) { echo " $row => ['" . implode("', '", $inner) . "']\n"; } echo "]\n"; } function matrix_make_square(array &$matrix) { $pad = count(array_keys($matrix)); foreach ($matrix as &$row) $row = array_pad($row, $pad, ''); } $tests = [ [ ['X'], ['X'] ], [ ['X'], ['X'], ['X'] ], [ [ 'X', '' ], [ '', 'X' ] ], [ ['X', 'Y', 'Z'], ['X', 'Y'], ['X']], [ ['X', 'Y'], ['X', 'Y'], ['X', 'Y'] ] ]; array_map(function ($matrix) { matrix_display($matrix); echo "solved by:" . PHP_EOL; matrix_brute_solve($matrix); echo PHP_EOL; }, $tests); с function matrix_is_solved(array $matrix) { foreach (array_keys(current($matrix)) as $offset) { $column = array_filter($raw = array_column($matrix, $offset)); if (count($column) != count(array_unique($column))) return false; } return true; } function matrix_generate_vectors(array $matrix) { $vectors = []; $columns = count(current($matrix)); $gen = function ($depth=0, $combo='') use (&$gen, &$vectors, $columns) { if ($depth < $columns) for ($i = 0; $i < $columns; $i++) $gen($depth + 1, $i . $combo); else $vectors[] = array_map('intval', str_split($combo)); }; $gen(); return $vectors; } function matrix_rotate(array $matrix, array $vector) { foreach ($matrix as $row => &$values) { array_rotate($values, $vector[$row]); } return $matrix; } function matrix_brute_solve(array $matrix) { matrix_make_square($matrix); foreach (matrix_generate_vectors($matrix) as $vector) { $attempt = matrix_rotate($matrix, $vector); if (matrix_is_solved($attempt)) return matrix_display($attempt); } echo 'No solution'; } function array_rotate(array &$array, $offset) { foreach (array_slice($array, 0, $offset) as $key => $val) { unset($array[$key]); $array[$key] = $val; } $array = array_values($array); } function matrix_display(array $matrix = null) { echo "[\n"; foreach ($matrix as $row => $inner) { echo " $row => ['" . implode("', '", $inner) . "']\n"; } echo "]\n"; } function matrix_make_square(array &$matrix) { $pad = count(array_keys($matrix)); foreach ($matrix as &$row) $row = array_pad($row, $pad, ''); } $tests = [ [ ['X'], ['X'] ], [ ['X'], ['X'], ['X'] ], [ [ 'X', '' ], [ '', 'X' ] ], [ ['X', 'Y', 'Z'], ['X', 'Y'], ['X']], [ ['X', 'Y'], ['X', 'Y'], ['X', 'Y'] ] ]; array_map(function ($matrix) { matrix_display($matrix); echo "solved by:" . PHP_EOL; matrix_brute_solve($matrix); echo PHP_EOL; }, $tests); 

И это отлично работает на малом массиве, но проблема в том, что это итерация по всем возможностям перемещений массивов, а для массива вроде 6×6 это слишком много для вычисления – O(n n ) как во времени, так и в пространстве!

Related of "Рекомендации по производительности для поиска уникальной перестановки"

После довольно возили я придумал следующий код.

Идея состоит в том, чтобы идентифицировать конфликтующие элементы и поменять их на столбе, где они больше не являются проблемой. В случаях, когда это не применимо, делается случайный выбор. Код работает рекурсивно и, следовательно, есть крайние случаи, когда требуется очень много времени для завершения.

Крайний крайний регистр – это вход, где все строки состоят из одинаковых значений.

 <?php declare(strict_types=1); final class SwapSolver { /** * @param array $input * * @return array */ public function solve(array $input): array { $input = array_values($input); return $this->swapDuplicates($this->prepare($input, $this->getMinRowLength($input))); } /** * @param array $input * * @return array */ private function swapDuplicates(array $input): array { $unswappable = []; foreach ($this->duplicates($input) as $position) { list($r, $a) = $position; $swapped = false; foreach ($this->swapCandidates($input, $r, $a, true) as $b) { $input[$r] = $this->swap($input[$r], $a, $b); $swapped = true; break; } if (!$swapped) { $unswappable[] = $position; } } // still unswappable $unswappable = array_values(array_filter($unswappable, function (array $position) use ($input): bool { return $this->isDuplicate($input, ...$position); })); // tie breaker if (count($unswappable) > 0) { list($r, $a) = $unswappable[mt_rand(0, count($unswappable) - 1)]; $candidates = []; foreach ($this->swapCandidates($input, $r, $a, false) as $b) { $candidates[] = $b; } $input[$r] = $this->swap($input[$r], $a, $candidates[mt_rand(0, count($candidates) - 1)]); return $this->swapDuplicates($input); } return $input; } /** * @param array $input * * @return \Generator */ private function duplicates(array &$input): \Generator { foreach ($input as $r => $row) { foreach ($row as $c => $value) { if ($this->isDuplicate($input, $r, $c)) { yield [$r, $c]; } } } } /** * @param array $input * @param int $row * @param int $column * * @return bool */ private function isDuplicate(array $input, int $row, int $column): bool { $candidate = $input[$row][$column]; if (is_null($candidate)) { return false; } foreach (array_column($input, $column) as $r => $value) { if ($r !== $row && $value === $candidate) { return true; } } return false; } /** * @param array $input * @param int $row * @param int $column * @param bool $strict * * @return \Generator */ private function swapCandidates(array &$input, int $row, int $column, bool $strict): \Generator { foreach ($input[$row] as $c => $dst) { if ((!$strict || !in_array($input[$row][$column], array_column($input, $c), true)) && (is_null($dst) || !in_array($dst, array_column($input, $column), true)) ) { yield $c; } } } /** * @param array $row * @param int $from * @param int $to * * @return array */ private function swap(array $row, int $from, int $to): array { $tmp = $row[$to]; $row[$to] = $row[$from]; $row[$from] = $tmp; return $row; } /** * @param array $input * @param int $padSize * * @return array */ private function prepare(array $input, int $padSize): array { return array_map(function (array $row) use ($padSize): array { $row = array_pad($row, $padSize, null); shuffle($row); return $row; }, $input); } /** * @param array $input * * @return int */ private function getMinRowLength(array $input): int { return max( ...array_values(array_count_values(array_merge(...$input))), ...array_map('count', $input) ); } } 

Применение:

 <?php $solver = new SwapSolver(); $solution = $solver->solve($input); 

Дополнительный код: https://github.com/Yoshix/so-47261385

На самом деле, солютон довольно прост. Вы проверяете количество уникальных символов, и это число значений в выходном массиве. Ниже код будет делать то, что вы хотите почти мгновенно.

Жесткая часть – удаление подстановочных знаков. Это то, что вы можете сделать только с помощью bruteforce, если хотите 100% уверенности. В приведенном ниже решении будет стараться, чтобы удалить все подстановочные знаки, переключая позиции несколько раз по порядку.

Это похоже на то, как google обрабатывает проблему с продавцом в своих OR-инструментах. Вам нужно найти лучшее сочетание между точностью и скоростью. Установив количество циклов выше в приведенной ниже функции, шансы на успех увеличиваются. Но это будет медленнее.

 /* HELPERS */ function ShowNice($output) { //nice output: echo '<pre>'; foreach($output as $key=>$val) { echo '<br />' . str_pad($key,2," ",STR_PAD_LEFT) . ' => ['; $first = true; foreach($val as $char) { if (!$first) { echo ', '; } echo "'".$char."'"; $first = false; } echo ']'; } echo '</pre>'; } function TestValid($output, $nullchar) { $keys = count($output[0]); for ($i=0;$i<$keys;$i++) { $found = []; foreach($output as $key=>$val) { $char = $val[$i]; if ($char==$nullchar) { continue; } if (array_key_exists($char, $found)) { return false; //this char was found before } $found[$char] = true; } } return true; } $input = [ 0 => ['X', 'Y', 'Z', 'I', 'J'], 1 => ['X', 'Y', 'Z', 'I'], 2 => ['X', 'Y', 'Z', 'I'], 3 => ['X', 'Y', 'Z', 'I'], 4 => ['X', 'Y', 'Z'], 5 => ['X', 'Y', 'Z'] ]; //generate large table $genLength = 30; //max double alphabet $innerLength = $genLength; $input2 = []; for($i=0;$i<$genLength;$i++) { $inner = []; if (rand(0, 1)==1) { $innerLength--; } for($c=0;$c<$innerLength;$c++) { $ascii = 65 + $c; //upper case if ($ascii>90) { $ascii += 6; //lower case } $inner[] = chr($ascii); } $input2[] = $inner; } //generate large table with different keys $genLength = 10; //max double alphabet $innerLength = $genLength; $input3 = []; for($i=0;$i<$genLength;$i++) { $inner = []; if (rand(0, 1)==1) { //comment o make same length inner arrays, but perhaps more distinct values //$innerLength--; } $nr = 0; for($c=0;$c<$innerLength;$c++) { $ascii = 65 + $c + $nr; //upper case if ($ascii>90) { $ascii += 6; //lower case } //$inner[] = chr($ascii); $inner[] = $c+$nr+1; //increase nr? if (rand(0, 2)==1) { $nr++; } } $input3[] = $inner; } //generate table with numeric values, to show what happens $genLength = 10; //max double alphabet $innerLength = $genLength; $input4 = []; for($i=0;$i<$genLength;$i++) { $inner = []; for($c=0;$c<$innerLength;$c++) { $inner[] = $c+1; } $input4[] = $inner; } $input5 = [ 0 => ['X', 'Y'], 1 => ['X', 'Y'], 2 => ['X', 'Y'], ]; $input6 = [ 0 => ['X', 'Y', 'Z', 'I', 'J'], 1 => ['X', 'Y', 'Z', 'I'], 2 => ['X', 'Y', 'Z', 'I'], 3 => ['X', 'Y', 'Z', 'I'], 4 => ['X', 'Y', 'Z'] ]; $input7 = [ ['X', 'Y', 'A', 'B'], ['X', 'Y', 'A', 'C'] ]; $input8 = [ ['X', 'Y', 'A'], ['X', 'Y', 'B'], ['X', 'Y', 'C'] ]; $input9 = [ ['X', 'Z', 'Y', 'A', 'E', 'D'], ['X', 'Z', 'Y', 'A', 'B'], ['X', 'Z', 'Y', 'A', 'C'], ['X', 'Z', 'Y', 'A', 'D'], ['X', 'Z', 'Y', 'A', 'D'], ['X', 'Z', 'Y', 'A', 'D'] ]; /* ACTUAL CODE */ CreateOutput($input, 1); function CreateOutput($input, $loops=0) { echo '<h2>Input</h2>'; ShowNice($input); //find all distinct chars //find maxlength for any inner array $distinct = []; $maxLength = 0; $minLength = -1; $rowCount = count($input); $flipped = []; $i = 1; foreach($input as $key=>$val) { if ($maxLength<count($val)) { $maxLength = count($val); } if ($minLength>count($val) || $minLength==-1) { $minLength = count($val); } foreach($val as $char) { if (!array_key_exists($char, $distinct)) { $distinct[$char] = $i; $i++; } } $flipped[$key] = array_flip($val); } //keep track of the count for actual chars $actualChars = $i-1; $nullchar = '_'; //add null values to distinct if ($minLength!=$maxLength && count($distinct)>$maxLength) { $char = '#'.$i.'#'; $distinct[$nullchar] = $i; //now it only gets add when a key is smaller, not if all are the same size $i++; } //if $distinct count is small then rowcount, we need more distinct $addForRowcount = (count($distinct)<$rowCount); while (count($distinct)<$rowCount) { $char = '#'.$i.'#'; $distinct[$char] = $i; $i++; } //flip the distinct array to make the index the keys $distinct = array_flip($distinct); $keys = count($distinct); //create output $output = []; $start = 0; foreach($input as $key=>$val) { $inner = []; for ($i=1;$i<=$keys;$i++) { $index = $start + $i; if ($index>$keys) { $index -= $keys; } if ($index>$actualChars) { //just add the null char $inner[] = $nullchar; } else { $char = $distinct[$index]; //check if the inner contains the char if (!array_key_exists($char, $flipped[$key])) { $char = $nullchar; } $inner[] = $char; } } $output[] = $inner; $start++; } echo '<h2>First output, unchecked</h2>'; ShowNice($output); $newOutput = $output; for ($x=0;$x<=$loops;$x++) { $newOutput = MoveLeft($newOutput, $nullchar); $newOutput = MoveLeft($newOutput, $nullchar, true); $newOutput = SwitchChar($newOutput, $nullchar); } echo '<h2>New output</h2>'; ShowNice($newOutput); //in $newoutput we moved all the invalid wildcards to the end //now we need to test if the last row has wildcards if (count($newOutput[0])<count($output[0])) { $output = $newOutput; } echo '<h2>Best result ('.(TestValid($output, $nullchar)?'VALID':'INVALID').')</h2>'; ShowNice($output); return $output; } function MoveLeft($newOutput, $nullchar, $reverse=false) { //see if we can remove more wildcards $lastkey = count($newOutput[0])-1; $testing = true; while ($testing) { $testing = false; //we decide if we go another round ob_deflatehandler $test = $newOutput; $lastkey = count($newOutput[0])-1; $start = 0; $end = count($test); if ($reverse) { $start = count($test)-1; $end = -1; } for($key = $start;$key!=$end;$key += ($reverse?-1:1) ) { $val = $test[$key]; $org = array_values($val); foreach($val as $i=>$char) { if ($char!=$nullchar) { continue; //we only test wildcards } $wildcardAtEnd=true; for($x=$i+1;$x<=$lastkey;$x++) { $nextChar = $val[$x]; if ($nextChar!=$nullchar) { $wildcardAtEnd = false; break; } } if ($wildcardAtEnd===true) { continue; //the char next to it must not be wildcard } //remove the wildcard and add it to the base64_encode unset($val[$i]); $val[] = $nullchar; $test[$key] = array_values($val); //correct order if (TestValid($test, $nullchar)) { //we can keep the new one $newOutput = $test; $testing = true; //keep testing, but start over to make sure we dont miss anything break 2; //break both foreach, not while } $test[$key] = $org; //reset original values before remove for next test } } } $allWildCards = true; while ($allWildCards) { $lastkey = count($newOutput[0])-1; foreach($newOutput as $key=>$val) { if ($val[$lastkey]!=$nullchar) { $allWildCards = false; break; } } if ($allWildCards) { foreach($newOutput as $key=>$val) { unset($val[$lastkey]); $newOutput[$key] = array_values($val); } $output = $newOutput; } } return $newOutput; } function SwitchChar($newOutput, $nullchar) { $switching = true; $switched = []; while($switching) { $switching = false; $test = $newOutput; $lastkey = count($newOutput[0])-1; foreach($test as $key=> $val) { foreach($val as $index=>$char) { $switched[$key][$index][$char] = true;//store the switches we make //see if can move the char somewhere else for($i=0;$i<=$lastkey;$i++) { if ($i==$index) { continue;//current pos } if (isset($switched[$key][$i][$char])) { continue; //been here before } $org = array_values($val); $switched[$key][$i][$char] = true; $t = $val[$i]; $val[$index] = $t; $val[$i] = $char; $test[$key] = array_values($val); if (TestValid($test, $nullchar)) { //echo '<br />VALID: ' . $key . ' - ' . $index . ' - ' . $i . ' - ' . $t . ' - ' . $char; $newOutput = MoveLeft($test, $nullchar); $switching = true; break 3;//for and two foreach } //echo '<br />INVALID: ' . $key . ' - ' . $index . ' - ' . $i . ' - ' . $t . ' - ' . $char; $val = $org; $test[$key] = $org; } } } } return $newOutput; } с /* HELPERS */ function ShowNice($output) { //nice output: echo '<pre>'; foreach($output as $key=>$val) { echo '<br />' . str_pad($key,2," ",STR_PAD_LEFT) . ' => ['; $first = true; foreach($val as $char) { if (!$first) { echo ', '; } echo "'".$char."'"; $first = false; } echo ']'; } echo '</pre>'; } function TestValid($output, $nullchar) { $keys = count($output[0]); for ($i=0;$i<$keys;$i++) { $found = []; foreach($output as $key=>$val) { $char = $val[$i]; if ($char==$nullchar) { continue; } if (array_key_exists($char, $found)) { return false; //this char was found before } $found[$char] = true; } } return true; } $input = [ 0 => ['X', 'Y', 'Z', 'I', 'J'], 1 => ['X', 'Y', 'Z', 'I'], 2 => ['X', 'Y', 'Z', 'I'], 3 => ['X', 'Y', 'Z', 'I'], 4 => ['X', 'Y', 'Z'], 5 => ['X', 'Y', 'Z'] ]; //generate large table $genLength = 30; //max double alphabet $innerLength = $genLength; $input2 = []; for($i=0;$i<$genLength;$i++) { $inner = []; if (rand(0, 1)==1) { $innerLength--; } for($c=0;$c<$innerLength;$c++) { $ascii = 65 + $c; //upper case if ($ascii>90) { $ascii += 6; //lower case } $inner[] = chr($ascii); } $input2[] = $inner; } //generate large table with different keys $genLength = 10; //max double alphabet $innerLength = $genLength; $input3 = []; for($i=0;$i<$genLength;$i++) { $inner = []; if (rand(0, 1)==1) { //comment o make same length inner arrays, but perhaps more distinct values //$innerLength--; } $nr = 0; for($c=0;$c<$innerLength;$c++) { $ascii = 65 + $c + $nr; //upper case if ($ascii>90) { $ascii += 6; //lower case } //$inner[] = chr($ascii); $inner[] = $c+$nr+1; //increase nr? if (rand(0, 2)==1) { $nr++; } } $input3[] = $inner; } //generate table with numeric values, to show what happens $genLength = 10; //max double alphabet $innerLength = $genLength; $input4 = []; for($i=0;$i<$genLength;$i++) { $inner = []; for($c=0;$c<$innerLength;$c++) { $inner[] = $c+1; } $input4[] = $inner; } $input5 = [ 0 => ['X', 'Y'], 1 => ['X', 'Y'], 2 => ['X', 'Y'], ]; $input6 = [ 0 => ['X', 'Y', 'Z', 'I', 'J'], 1 => ['X', 'Y', 'Z', 'I'], 2 => ['X', 'Y', 'Z', 'I'], 3 => ['X', 'Y', 'Z', 'I'], 4 => ['X', 'Y', 'Z'] ]; $input7 = [ ['X', 'Y', 'A', 'B'], ['X', 'Y', 'A', 'C'] ]; $input8 = [ ['X', 'Y', 'A'], ['X', 'Y', 'B'], ['X', 'Y', 'C'] ]; $input9 = [ ['X', 'Z', 'Y', 'A', 'E', 'D'], ['X', 'Z', 'Y', 'A', 'B'], ['X', 'Z', 'Y', 'A', 'C'], ['X', 'Z', 'Y', 'A', 'D'], ['X', 'Z', 'Y', 'A', 'D'], ['X', 'Z', 'Y', 'A', 'D'] ]; /* ACTUAL CODE */ CreateOutput($input, 1); function CreateOutput($input, $loops=0) { echo '<h2>Input</h2>'; ShowNice($input); //find all distinct chars //find maxlength for any inner array $distinct = []; $maxLength = 0; $minLength = -1; $rowCount = count($input); $flipped = []; $i = 1; foreach($input as $key=>$val) { if ($maxLength<count($val)) { $maxLength = count($val); } if ($minLength>count($val) || $minLength==-1) { $minLength = count($val); } foreach($val as $char) { if (!array_key_exists($char, $distinct)) { $distinct[$char] = $i; $i++; } } $flipped[$key] = array_flip($val); } //keep track of the count for actual chars $actualChars = $i-1; $nullchar = '_'; //add null values to distinct if ($minLength!=$maxLength && count($distinct)>$maxLength) { $char = '#'.$i.'#'; $distinct[$nullchar] = $i; //now it only gets add when a key is smaller, not if all are the same size $i++; } //if $distinct count is small then rowcount, we need more distinct $addForRowcount = (count($distinct)<$rowCount); while (count($distinct)<$rowCount) { $char = '#'.$i.'#'; $distinct[$char] = $i; $i++; } //flip the distinct array to make the index the keys $distinct = array_flip($distinct); $keys = count($distinct); //create output $output = []; $start = 0; foreach($input as $key=>$val) { $inner = []; for ($i=1;$i<=$keys;$i++) { $index = $start + $i; if ($index>$keys) { $index -= $keys; } if ($index>$actualChars) { //just add the null char $inner[] = $nullchar; } else { $char = $distinct[$index]; //check if the inner contains the char if (!array_key_exists($char, $flipped[$key])) { $char = $nullchar; } $inner[] = $char; } } $output[] = $inner; $start++; } echo '<h2>First output, unchecked</h2>'; ShowNice($output); $newOutput = $output; for ($x=0;$x<=$loops;$x++) { $newOutput = MoveLeft($newOutput, $nullchar); $newOutput = MoveLeft($newOutput, $nullchar, true); $newOutput = SwitchChar($newOutput, $nullchar); } echo '<h2>New output</h2>'; ShowNice($newOutput); //in $newoutput we moved all the invalid wildcards to the end //now we need to test if the last row has wildcards if (count($newOutput[0])<count($output[0])) { $output = $newOutput; } echo '<h2>Best result ('.(TestValid($output, $nullchar)?'VALID':'INVALID').')</h2>'; ShowNice($output); return $output; } function MoveLeft($newOutput, $nullchar, $reverse=false) { //see if we can remove more wildcards $lastkey = count($newOutput[0])-1; $testing = true; while ($testing) { $testing = false; //we decide if we go another round ob_deflatehandler $test = $newOutput; $lastkey = count($newOutput[0])-1; $start = 0; $end = count($test); if ($reverse) { $start = count($test)-1; $end = -1; } for($key = $start;$key!=$end;$key += ($reverse?-1:1) ) { $val = $test[$key]; $org = array_values($val); foreach($val as $i=>$char) { if ($char!=$nullchar) { continue; //we only test wildcards } $wildcardAtEnd=true; for($x=$i+1;$x<=$lastkey;$x++) { $nextChar = $val[$x]; if ($nextChar!=$nullchar) { $wildcardAtEnd = false; break; } } if ($wildcardAtEnd===true) { continue; //the char next to it must not be wildcard } //remove the wildcard and add it to the base64_encode unset($val[$i]); $val[] = $nullchar; $test[$key] = array_values($val); //correct order if (TestValid($test, $nullchar)) { //we can keep the new one $newOutput = $test; $testing = true; //keep testing, but start over to make sure we dont miss anything break 2; //break both foreach, not while } $test[$key] = $org; //reset original values before remove for next test } } } $allWildCards = true; while ($allWildCards) { $lastkey = count($newOutput[0])-1; foreach($newOutput as $key=>$val) { if ($val[$lastkey]!=$nullchar) { $allWildCards = false; break; } } if ($allWildCards) { foreach($newOutput as $key=>$val) { unset($val[$lastkey]); $newOutput[$key] = array_values($val); } $output = $newOutput; } } return $newOutput; } function SwitchChar($newOutput, $nullchar) { $switching = true; $switched = []; while($switching) { $switching = false; $test = $newOutput; $lastkey = count($newOutput[0])-1; foreach($test as $key=> $val) { foreach($val as $index=>$char) { $switched[$key][$index][$char] = true;//store the switches we make //see if can move the char somewhere else for($i=0;$i<=$lastkey;$i++) { if ($i==$index) { continue;//current pos } if (isset($switched[$key][$i][$char])) { continue; //been here before } $org = array_values($val); $switched[$key][$i][$char] = true; $t = $val[$i]; $val[$index] = $t; $val[$i] = $char; $test[$key] = array_values($val); if (TestValid($test, $nullchar)) { //echo '<br />VALID: ' . $key . ' - ' . $index . ' - ' . $i . ' - ' . $t . ' - ' . $char; $newOutput = MoveLeft($test, $nullchar); $switching = true; break 3;//for and two foreach } //echo '<br />INVALID: ' . $key . ' - ' . $index . ' - ' . $i . ' - ' . $t . ' - ' . $char; $val = $org; $test[$key] = $org; } } } } return $newOutput; } с /* HELPERS */ function ShowNice($output) { //nice output: echo '<pre>'; foreach($output as $key=>$val) { echo '<br />' . str_pad($key,2," ",STR_PAD_LEFT) . ' => ['; $first = true; foreach($val as $char) { if (!$first) { echo ', '; } echo "'".$char."'"; $first = false; } echo ']'; } echo '</pre>'; } function TestValid($output, $nullchar) { $keys = count($output[0]); for ($i=0;$i<$keys;$i++) { $found = []; foreach($output as $key=>$val) { $char = $val[$i]; if ($char==$nullchar) { continue; } if (array_key_exists($char, $found)) { return false; //this char was found before } $found[$char] = true; } } return true; } $input = [ 0 => ['X', 'Y', 'Z', 'I', 'J'], 1 => ['X', 'Y', 'Z', 'I'], 2 => ['X', 'Y', 'Z', 'I'], 3 => ['X', 'Y', 'Z', 'I'], 4 => ['X', 'Y', 'Z'], 5 => ['X', 'Y', 'Z'] ]; //generate large table $genLength = 30; //max double alphabet $innerLength = $genLength; $input2 = []; for($i=0;$i<$genLength;$i++) { $inner = []; if (rand(0, 1)==1) { $innerLength--; } for($c=0;$c<$innerLength;$c++) { $ascii = 65 + $c; //upper case if ($ascii>90) { $ascii += 6; //lower case } $inner[] = chr($ascii); } $input2[] = $inner; } //generate large table with different keys $genLength = 10; //max double alphabet $innerLength = $genLength; $input3 = []; for($i=0;$i<$genLength;$i++) { $inner = []; if (rand(0, 1)==1) { //comment o make same length inner arrays, but perhaps more distinct values //$innerLength--; } $nr = 0; for($c=0;$c<$innerLength;$c++) { $ascii = 65 + $c + $nr; //upper case if ($ascii>90) { $ascii += 6; //lower case } //$inner[] = chr($ascii); $inner[] = $c+$nr+1; //increase nr? if (rand(0, 2)==1) { $nr++; } } $input3[] = $inner; } //generate table with numeric values, to show what happens $genLength = 10; //max double alphabet $innerLength = $genLength; $input4 = []; for($i=0;$i<$genLength;$i++) { $inner = []; for($c=0;$c<$innerLength;$c++) { $inner[] = $c+1; } $input4[] = $inner; } $input5 = [ 0 => ['X', 'Y'], 1 => ['X', 'Y'], 2 => ['X', 'Y'], ]; $input6 = [ 0 => ['X', 'Y', 'Z', 'I', 'J'], 1 => ['X', 'Y', 'Z', 'I'], 2 => ['X', 'Y', 'Z', 'I'], 3 => ['X', 'Y', 'Z', 'I'], 4 => ['X', 'Y', 'Z'] ]; $input7 = [ ['X', 'Y', 'A', 'B'], ['X', 'Y', 'A', 'C'] ]; $input8 = [ ['X', 'Y', 'A'], ['X', 'Y', 'B'], ['X', 'Y', 'C'] ]; $input9 = [ ['X', 'Z', 'Y', 'A', 'E', 'D'], ['X', 'Z', 'Y', 'A', 'B'], ['X', 'Z', 'Y', 'A', 'C'], ['X', 'Z', 'Y', 'A', 'D'], ['X', 'Z', 'Y', 'A', 'D'], ['X', 'Z', 'Y', 'A', 'D'] ]; /* ACTUAL CODE */ CreateOutput($input, 1); function CreateOutput($input, $loops=0) { echo '<h2>Input</h2>'; ShowNice($input); //find all distinct chars //find maxlength for any inner array $distinct = []; $maxLength = 0; $minLength = -1; $rowCount = count($input); $flipped = []; $i = 1; foreach($input as $key=>$val) { if ($maxLength<count($val)) { $maxLength = count($val); } if ($minLength>count($val) || $minLength==-1) { $minLength = count($val); } foreach($val as $char) { if (!array_key_exists($char, $distinct)) { $distinct[$char] = $i; $i++; } } $flipped[$key] = array_flip($val); } //keep track of the count for actual chars $actualChars = $i-1; $nullchar = '_'; //add null values to distinct if ($minLength!=$maxLength && count($distinct)>$maxLength) { $char = '#'.$i.'#'; $distinct[$nullchar] = $i; //now it only gets add when a key is smaller, not if all are the same size $i++; } //if $distinct count is small then rowcount, we need more distinct $addForRowcount = (count($distinct)<$rowCount); while (count($distinct)<$rowCount) { $char = '#'.$i.'#'; $distinct[$char] = $i; $i++; } //flip the distinct array to make the index the keys $distinct = array_flip($distinct); $keys = count($distinct); //create output $output = []; $start = 0; foreach($input as $key=>$val) { $inner = []; for ($i=1;$i<=$keys;$i++) { $index = $start + $i; if ($index>$keys) { $index -= $keys; } if ($index>$actualChars) { //just add the null char $inner[] = $nullchar; } else { $char = $distinct[$index]; //check if the inner contains the char if (!array_key_exists($char, $flipped[$key])) { $char = $nullchar; } $inner[] = $char; } } $output[] = $inner; $start++; } echo '<h2>First output, unchecked</h2>'; ShowNice($output); $newOutput = $output; for ($x=0;$x<=$loops;$x++) { $newOutput = MoveLeft($newOutput, $nullchar); $newOutput = MoveLeft($newOutput, $nullchar, true); $newOutput = SwitchChar($newOutput, $nullchar); } echo '<h2>New output</h2>'; ShowNice($newOutput); //in $newoutput we moved all the invalid wildcards to the end //now we need to test if the last row has wildcards if (count($newOutput[0])<count($output[0])) { $output = $newOutput; } echo '<h2>Best result ('.(TestValid($output, $nullchar)?'VALID':'INVALID').')</h2>'; ShowNice($output); return $output; } function MoveLeft($newOutput, $nullchar, $reverse=false) { //see if we can remove more wildcards $lastkey = count($newOutput[0])-1; $testing = true; while ($testing) { $testing = false; //we decide if we go another round ob_deflatehandler $test = $newOutput; $lastkey = count($newOutput[0])-1; $start = 0; $end = count($test); if ($reverse) { $start = count($test)-1; $end = -1; } for($key = $start;$key!=$end;$key += ($reverse?-1:1) ) { $val = $test[$key]; $org = array_values($val); foreach($val as $i=>$char) { if ($char!=$nullchar) { continue; //we only test wildcards } $wildcardAtEnd=true; for($x=$i+1;$x<=$lastkey;$x++) { $nextChar = $val[$x]; if ($nextChar!=$nullchar) { $wildcardAtEnd = false; break; } } if ($wildcardAtEnd===true) { continue; //the char next to it must not be wildcard } //remove the wildcard and add it to the base64_encode unset($val[$i]); $val[] = $nullchar; $test[$key] = array_values($val); //correct order if (TestValid($test, $nullchar)) { //we can keep the new one $newOutput = $test; $testing = true; //keep testing, but start over to make sure we dont miss anything break 2; //break both foreach, not while } $test[$key] = $org; //reset original values before remove for next test } } } $allWildCards = true; while ($allWildCards) { $lastkey = count($newOutput[0])-1; foreach($newOutput as $key=>$val) { if ($val[$lastkey]!=$nullchar) { $allWildCards = false; break; } } if ($allWildCards) { foreach($newOutput as $key=>$val) { unset($val[$lastkey]); $newOutput[$key] = array_values($val); } $output = $newOutput; } } return $newOutput; } function SwitchChar($newOutput, $nullchar) { $switching = true; $switched = []; while($switching) { $switching = false; $test = $newOutput; $lastkey = count($newOutput[0])-1; foreach($test as $key=> $val) { foreach($val as $index=>$char) { $switched[$key][$index][$char] = true;//store the switches we make //see if can move the char somewhere else for($i=0;$i<=$lastkey;$i++) { if ($i==$index) { continue;//current pos } if (isset($switched[$key][$i][$char])) { continue; //been here before } $org = array_values($val); $switched[$key][$i][$char] = true; $t = $val[$i]; $val[$index] = $t; $val[$i] = $char; $test[$key] = array_values($val); if (TestValid($test, $nullchar)) { //echo '<br />VALID: ' . $key . ' - ' . $index . ' - ' . $i . ' - ' . $t . ' - ' . $char; $newOutput = MoveLeft($test, $nullchar); $switching = true; break 3;//for and two foreach } //echo '<br />INVALID: ' . $key . ' - ' . $index . ' - ' . $i . ' - ' . $t . ' - ' . $char; $val = $org; $test[$key] = $org; } } } } return $newOutput; } 

Результат:

 Input 0 => ['X', 'Y', 'A'] 1 => ['X', 'Y', 'B'] 2 => ['X', 'Y', 'C'] First output, unchecked 0 => ['X', 'Y', 'A', '_', '_'] 1 => ['Y', '_', 'B', '_', 'X'] 2 => ['_', '_', 'C', 'X', 'Y'] New output 0 => ['X', 'Y', 'A', '_', '_'] 1 => ['Y', 'B', 'X', '_', '_'] 2 => ['C', 'X', 'Y', '_', '_'] Best result (VALID) 0 => ['X', 'Y', 'A'] 1 => ['Y', 'B', 'X'] 2 => ['C', 'X', 'Y'] 

то, что вы должны попытаться использовать, называется Power Set, который:

из википедии в математике набор мощности (или силовой набор) любого множества S является множеством всех подмножеств S, включая пустое множество и само S, по-разному обозначаемое как P (S), 𝒫 (S), ℘ (S) (используя «Weierstrass p»), P (S), ℙ (S), или, отождествляя набор S с множеством всех функций из S в заданный набор из двух элементов, 2S.

если есть набор {a,b,c} он даст результаты:

 {{a,b,c},{a,b},{a,c},{b,c},{a},{b},{c}} 

полезная библиотека php от github даст правильные результаты, которые вы ищете в приведенных выше правилах, если не все применяемые правила вы также можете попытаться добавить фильтры к результатам, чтобы получить их право.

Основываясь на ответе на предыдущий вопрос, который вы предоставили, это может быть решено (в этом случае) более элегантно, используя несколько встроенных функций, которые PHP имеет для поддержки массива. Это, вероятно, лучший из любого языка.

 function solve($matrix){ $master = []; $_matrix = []; foreach($matrix as $key => $array){ $_matrix[$key] = array_combine($array,$array); $master += $_matrix[$key]; } $default = array_fill_keys($master, ''); $result = []; foreach($_matrix as $array){ $result[] = array_values(array_merge($default, $array)); } print_r($result); } 

Используя те же тесты

 $tests = [ [ ['X'], ['X'] ], [ ['X'], ['X'], ['X'] ], [ [ 'X', '' ], [ '', 'X' ] ], [ ['X', 'Y', 'Z'], ['X', 'Y'], ['X']], [ ['X', 'Y'], ['X', 'Y'], ['X', 'Y'] ], [ ['X', 'Y', 'Z'], ['X', 'Y', 'Z'], ['X', 'Y', 'Z'] ], [ ['X', 'Y', 'Z', 'I', 'J'], ['X', 'Y', 'Z', 'I'], ['X', 'Y', 'Z', 'I'], ['X', 'Y', 'Z', 'I'], ['X', 'Y', 'Z'], ['X', 'Y', 'Z'] ], ]; array_map(function ($matrix) { solve($matrix); }, $tests); 

Это то, что я получаю в сравнении

 [ 0 => ['X', 'Y', 'Z', 'I', 'J'] //<- contains all unique values 1 => ['X', 'Y', 'Z', 'I'] 2 => ['X', 'Y', 'Z', 'I'] 3 => ['X', 'Y', 'Z', 'I'] 4 => ['X', 'Y', 'Z'] 5 => ['X', 'Y', 'Z'] ] Their Result: [ 0 => ['', 'X', 'Y', 'Z', 'I', 'J'] //<- contains an extra '' empty value 1 => ['', '', 'X', 'Y', 'Z', 'I'] 2 => ['I', '', '', 'X', 'Y', 'Z'] 3 => ['Z', 'I', '', '', 'X', 'Y'] 4 => ['Y', 'Z', '', '', '', 'X'] 5 => ['X', 'Y', 'Z', '', '', ''] ] My Result [ 0 => ['X', 'Y', 'Z', 'I', 'J'] 1 => ['X', 'Y', 'Z', 'I', ''] 2 => ['X', 'Y', 'Z', 'I', ''] 3 => ['X', 'Y', 'Z', 'I', ''] 4 => ['X', 'Y', 'Z','',''] 5 => ['X', 'Y', 'Z','',''] ] 

Вы можете проверить его здесь.

http://sandbox.onlinephpfunctions.com/code/86d0b4332963f95449df2e7d4d47fcd8224fe45d

Я даже приурочил его к использованию microtime

шахта 0.00013017654418945 миллисекунды

их 0.10895299911499 миллисекунд

Это не удивительно, так как у них около 60 строк кода и 7 вызовов функций. Mine – это только 1 функция 14 строк кода.

Тем не менее я не знаю, важна ли позиция значений в выходе. Не то, что вы ожидаете, так как результат расширяет этот вопрос.

Дело в том, что они также теряют положение индекса, просто посмотрите на второй массив в их результате, 2 => ['I', '', '', 'X', 'Y', 'Z'] по сравнению со входом 2 => ['X', 'Y', 'Z', 'I'] . И я не буду упоминать лишние '' в выходе, которые, вероятно, не принадлежат.

Может быть, я что-то пропустил, LOL, я обычно не делаю эти вещи типа math-y.

UPDATE, если вы хотите объяснить, как это работает,

  • array_combine($array,$array); создает массив с совпадающим значением key =>, мы злоупотребляем тем фактом, что ключи массива уникальны по своей природе. Подобным образом ['X'=>'X','Y'=>'Y'...]
  • то мы строим массив «master» со всеми значениями в нем и сопоставленными ключами, один массив, чтобы управлять ими всеми. Основной массив ограничен по размеру максимальным числом или уникальными значениями, потому что мы используем ключи для устранения дубликатов.
  • то мы используем array_fill_keys($master, ''); для сортировки создания шаблона всех значений. Ключи «мастера» – это все уникальные значения во всех внутренних массивах, поэтому мы заполняем его «подстановочным» заполнителем. В этом случае это выглядит так: ['X'=>'', 'Y'=>'', 'Z'=>'', 'I'=>'', 'J'=>'']
  • то мы объединим «модифицированный» исходный массив, также злоупотребляя ключами массива для нашего преимущества, заменив заполнители в «шаблонизированном» основном массиве «модифицированным» исходным массивом, поскольку ключи совпадают.
  • В последнем случае мы array_values ключи из массива с помощью array_values

И мы остаемся с каждым внутренним массивом «templated» от главного массива, но с исходными значениями, заполненными, а отсутствующие – пустыми.

надеюсь, это решение, которое мы ищем, у меня есть запрос для всех, чтобы проверить мое решение

код:

 <?php $input = [ [ 'F', 'I', 'J', 'Z' ], [ 'F', 'R', 'U', 'V' ], [ 'I', 'R', 'U', 'V' ], [ 'M', 'P', 'U', 'V' ], ]; do { $result = calculate($input); } while(!TestValid($input, $result, '_')); echo (TestValid($input, $result, '_')) ? 'VALID' : 'INVALID'; ShowNice($result); function TestValid($input, $output, $nullchar) { foreach($output as $k => $o) { $test = array_filter($o, function($v) use ($nullchar) { return $v != $nullchar; }); if (count($test) != count($input[$k])) { return false; } } return true; } function ShowNice($output) { $height = getHeight($output); foreach($output as $k => $v ) { for($i = 0;$i < $height;$i ++) { if (!isset($output[$k][$i])) { $output[$k][$i] = '_'; } } } echo '<pre>'; foreach($output as $key=>$val) { echo '<br />' . str_pad($key,2," ",STR_PAD_LEFT) . ' => ['; ksort($val); echo join(', ', $val); echo ']'; } echo '</pre>'; } function calculate($array) { echo "<pre>"; $full = getFullList($array); foreach($full as $f) { $frequency[$f] = getFrequency($array, $f); } // uksort($frequency, function($i, $j) use ($frequency) { // return $frequency[$j] <=> $frequency[$i]; // }); $frequency = array_keys($frequency); shuffle($frequency); $height = getHeight($array); foreach($array as $k => $v ) { for($i = 0;$i < $height;$i ++) { if (!isset($array[$k][$i])) $array[$k][$i] = '_'; } } foreach($array as $key => $value ) { $output[$key] = []; $used[$key] = []; } foreach($array as $key => $value ) { foreach($frequency as $k => $v) { $j = 0; foreach($array as $kk => $col) { if (in_array($v, $col)) { for ($h = 0; $h <= $height; $h++) { if (!isset($_h[$v][$kk])) { $_h[$v][$kk] = 0; } if ($h + $_h[$v][$kk] >= $height) { $hh = ($h + $_h[$v][$kk]) - $height; } else { $hh = $h + $_h[$v][$kk]; } $row = getRow($output, $hh); if (!in_array($v, $row) && !in_array($v, $used[$kk])) { if (!isset($output[$kk][$hh]) || $output[$kk][$hh] == '_') { $output[$kk][$hh] = $v; $used[$kk][] = $v; $keys = array_keys($array); foreach($keys as $i => $ke) { if ($ke == $kk) { if(isset($keys[$i+1])) { $_h[$v][$keys[$i + 1]] = $hh; } else { $_h[$v][$keys[0]] = $hh; } } } $unused[$kk] = array_diff($col, $used[$kk]); $j++; break; } } } } } // ShowNice($output); } } foreach($output as $k => $v ) { for($i = 0;$i < $height;$i ++) { if (!isset($output[$k][$i])) $output[$k][$i] = '_'; } } foreach($output as $k => $o) { ksort($output[$k]); } return $output; } function getHeight($array) { $heights = []; $max3 = count($array); $max2 = 0; foreach($array as $v) { if ($max2 < count($v)) { $max2 = count($v); } foreach ($v as $e) { $heights[$e] = (isset($heights[$e])) ? $heights[$e] + 1 : 1; } } $max = 0; foreach ($heights as $h ) { if ($h > $max) { $max = $h; } } return max($max, $max2, $max3); } function getRow($array, $row) { $res = []; foreach ($array as $a) { if (is_array($a)) { foreach($a as $k =>$b) { if ($row == $k) { $res[] = $b; } } } } return $res; } function getFrequency($array, $value) { $c=0; foreach ($array as $key => $v) { foreach ($v as $e) { if ($e == $value) $c++; } } return $c; } function getFullList($array) { $m = []; foreach($array as $a) { $m = array_merge($m, $a); } return array_unique($m); } 

ОБНОВЛЕНО

Это может быть окончательный, проверьте пожалуйста: детская площадка : https://eval.in/906355