【问题标题】:Performance tips for finding unique permutation查找唯一排列的性能提示
【发布时间】:2018-04-25 23:04:31
【问题描述】:

TLDR: how to find multidimensional array permutation in php 以及如何针对更大的数组进行优化?

这是这个问题的延续: how to find multidimensional array permutation in php

我们有排序数组的脚本,想法是找到数组的唯一排列,找到这个排列的规则是:

  1. 输入数组包含一组数组。
  2. 每个内部数组都包含唯一的元素。
  3. 每个内部数组可能有不同的长度和不同的值。
  4. 输出数组必须包含完全相同的值。
  5. 输出内部数组必须在同一个键上具有唯一值。
  6. 如果没有解决方案,则允许使用通配符ie.: null
  7. 通配符可以在同一个键上重复。
  8. 解决方案应使用尽可能少的通配符。
  9. 算法应该能够在 180 秒内处理最大 30x30 的数组。

到目前为止我有这个解决方案:

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);

这在小型阵列上完美运行,但麻烦在于迭代阵列移动的所有可能性,对于像 6x6 这样的阵列来说,计算量太大 - O(n<sup>n</sup>) 在两个时间 空间!

【问题讨论】:

  • 在前面的问题中,您提到了一个 30x30 的目标阵列。您可以发布该内容或同等内容以针对其开发解决方案吗?
  • 你需要一个排列,对吧?不是所有的排列?
  • 我需要匹配所有规则的单个(第一个)排列,是的
  • 嗯,是的,但是除了量子计算机之外,必须有解决方案,那些日子人们飞向太空......
  • 那么如果我理解你试图生成一个包含所有子数组中所有可能值的数组的问题,如前面提到的问题中所述?

标签: php arrays performance sorting


【解决方案1】:

您应该尝试使用的称为 Power set,即:

from wikipedia 在数学中,任何集合 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}}


来自github 的有用 php 库将在上述规则中提供您正在寻找的正确结果,如果未应用所有规则,您也可以尝试在结果上添加过滤器以使其正确。

【讨论】:

    【解决方案2】:

    解决方案实际上很简单。您检查唯一字符的数量,这就是输出数组中的值的数量。下面的代码几乎可以立即执行您想要的操作。

    最困难的部分是删除通配符。如果您想要 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']
    

    【讨论】:

    • 你是对的@Yoshi,但它有简单的修复。更新了!
    • 感谢,但在您的 $input 数组上,数组 1 和 5 中的键 0 上两次得到 X
    • @DorienCragen:啊,我明白了。那是因为我删除了对角线的更新。我将这部分注释掉,然后它再次起作用。我将在一秒钟内更新此检查以解决该部分问题
    • 酷,它通过了第一次测试,我会仔细检查它,但到目前为止看起来不错,不用担心,如果这个结束我会再次提供赏金
    • 好的,我发现了问题 :) 数组 [ ['X', 'Y', 'A', 'B'], ['X', 'Y', 'A', 'C'], ] 最佳解决方案可能没有通配符,但这应该很容易修复
    【解决方案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 =&gt; ['I', '', '', 'X', 'Y', 'Z'] 与输入 2 =&gt; ['X', 'Y', 'Z', 'I'] 相比。我不会在输出中提及可能不属于那里的额外''

    也许我错过了什么,哈哈,我通常不做这些数学类型的事情。

    更新如果您想了解其工作原理,

    • array_combine($array,$array); 创建一个具有匹配键 => 值的数组,我们滥用了数组键本质上是唯一的这一事实。像这样['X'=&gt;'X','Y'=&gt;'Y'...]
    • 然后我们构建一个“主”数组,其中包含所有值和匹配的键,一个数组来统治它们。主数组的大小限制为最大数量或唯一值,因为我们使用键来消除重复项。
    • 然后我们使用array_fill_keys($master, ''); 来创建一个包含所有值的模板。 “master”的键是所有内部数组中的所有唯一值,所以我们用“通配符”占位符填充它。在这种情况下,它看起来像这样['X'=&gt;'', 'Y'=&gt;'', 'Z'=&gt;'', 'I'=&gt;'', 'J'=&gt;'']
    • 然后我们合并“修改后的”原始数组,同时为了我们的优势滥用数组键,将“模板化”主数组中的占位符替换为“修改后的”原始数组,因为键匹配。
    • 最后我们使用array_values从数组中删除键

    我们留下了由主数组“模板化”的每个内部数组,但填充了原始值,缺失的值为空。

    【讨论】:

    • 您的输出不符合 Output inner array must have unique values on same key 。第一个位置有所有的X,第二个是Y,等等。目标是在第一个位置只得到一个X,在第二个位置得到一个Y,等等。
    【解决方案4】:

    希望这是我们正在寻找的解决方案,我有请求让大家检查我的解决方案

    代码:

    <?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);
    }
    

    更新

    这可能是决赛,请查看:playgroundhttps://eval.in/906355

    【讨论】:

    • @Yoshi 很好,谢谢,现在不知道如何解决,但我明白了
    • 根据我的测试,对于输入行多于列的所有情况,它似乎都失败了,例如:AY, LN, OS,但这似乎是一个小错误。
    • @Yoshi 如果最后的通配符可以忽略怎么办?看起来不错
    • @Yoshi 我也解决了这个问题,不是最终的,但应该适用于您的 4x4 :)
    • 它看起来很有希望,虽然对于 9x9,这在我的机器上大约需要 10-30 秒,并且还没有完成 10x10。
    【解决方案5】:

    经过一番折腾,我想出了以下代码。

    这个想法是识别冲突元素并将它们交换到不再是问题的列。对于不适用的情况,将进行随机选择。代码以递归方式工作,因此存在需要很长时间才能完成的极端情况。

    极端情况是所有行包含完全相同的值的输入。

    <?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

    【讨论】:

    • 谢谢,这是我正在寻找的解决方案,希望你喜欢它:)
    • @DorienCragen 不客气!如果你不介意我问,我很好奇,你需要这个做什么?
    • 这将是快速约会匹配地点解决方案的一部分
    猜你喜欢
    • 2016-11-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-30
    • 1970-01-01
    相关资源
    最近更新 更多