TLDR: how to find multidimensional array permutation in php以及如何优化更大的数组?
这是这个问题的延续: how to find multidimensional array permutation in php
我们有用于排序数组的脚本,想法是找到数组的唯一排列,找到这个排列的规则是:
- 输入数组包含一组数组。
- 每个内部数组都包含唯一元素。
- 每个内部数组可能有不同的长度和不同的值。
- 输出数组必须包含完全相同的值。
- 输出内部数组必须在同一个键上具有唯一值。
- 如果没有解决方案,则允许使用通配符
string ChaineDeSelction = "SELECT fir_mdnt FROM -- WHERE fir_aktiv = 1 AND EXISTS(select firbpv_fir from --)"; OdbcConnection MyConnec = new OdbcConnection(MyConnString); MyConnec.Open(); OdbcCommand MyComm = new OdbcCommand(ChaineDeSelction, MyConnec); DataTable.Load(MyComm.ExecuteReader()); _cb_Societe.DataSource = DataTable; <--- I tried this way the the foreach .Add way but both return the same. foreach(DataRow Ligne in DataTable.Rows) { _cb_Societe.Items.Add(Ligne); }
。- 可以在同一个密钥上复制通配符。
- 解决方案应该包含尽可能少的通配符。
- 算法应该能够在不到180秒的时间内处理 30x30 的阵列。
醇>
到目前为止我有这个解决方案:
ie.: null
这适用于小数组,但麻烦的是迭代所有可能的数组移动,对于像6x6这样的数组,这太多了,无法计算 - 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);
时间和 space!
答案 0 :(得分:7)
实际上解决方案非常简单。您可以检查唯一字符的数量以及输出数组中值的数量。下面的代码几乎可以立即执行您想要的操作。
困难的部分是删除通配符。如果您想要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']
答案 1 :(得分:4)
您应该尝试使用的是电源设置,即:
在数学中,任何集合S的幂集(或幂集)都是集合 S的所有子集,包括空集和S本身,各种各样 表示为P(S),(S),℘(S)(使用“Weierstrass p”),P(S),ℙ(S), 或者,用S中所有函数的集合识别S的powerset 给定一组两个元素,2S。
如果有一组{a,b,c}
,它会得到以下结果:
{{a,b,c},{a,b},{a,c},{b,c},{a},{b},{c}}
来自from wikipedia的有用的PHP库将在上述规则中提供您正在寻找的正确结果,如果不是所有规则都适用,您也可以尝试在结果上添加过滤器以使其正确。
答案 2 :(得分:3)
根据您提供的上一个问题的答案,可以使用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个函数调用。我只有1行14行代码。
那就是说我不知道值的位置在输出中是否重要。也不完全是你期望的产出扩展到这个问题。
事实是他们也失去了索引位置,只需查看结果中的第二个数组2 => ['I', '', '', 'X', 'Y', 'Z']
与输入2 => ['X', 'Y', 'Z', 'I']
进行比较。我不会在输出中提及可能不属于那里的额外''
。
也许我错过了一些东西,哈哈,我通常不会做这些数学类型的东西。
更新如果您想要解释其工作原理,
array_combine($array,$array);
创建一个匹配key =&gt;的数组值,我们滥用了数组键本质上是唯一的这一事实。像['X'=>'X','Y'=>'Y'...]
array_fill_keys($master, '');
来创建所有值的模板。 &#34; master&#34;的关键是所有内部数组中的所有唯一值,因此我们将其填入我们的#34;通配符&#34;占位符。在这种情况下,它看起来像['X'=>'', 'Y'=>'', 'Z'=>'', 'I'=>'', 'J'=>'']
array_values
我们留下每个内部阵列&#34;模板化&#34;由主数组但填充原始值并且缺少的值为空。
答案 3 :(得分:1)
希望这是我们正在寻找的解决方案,我请求让所有人检查我的解决方案
代码:
<?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