解决方案实际上很简单。您检查唯一字符的数量,这就是输出数组中的值的数量。下面的代码几乎可以立即执行您想要的操作。
最困难的部分是删除通配符。如果您想要 100% 的确定性,那么您只能使用蛮力来做到这一点。下面的解决方案会尽量按顺序切换位置数次,将所有通配符去掉。
这类似于 google 在其 OR 工具中处理 Traveling Salesman Problem 的方式。您需要找到准确性和速度之间的最佳组合。通过在下面的函数中将循环计数设置得更高,成功的机会就会增加。但是会慢一些。
/* 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']