Php 从多个值列表中查找所有非冲突的值组合

Php 从多个值列表中查找所有非冲突的值组合,php,algorithm,combinations,Php,Algorithm,Combinations,我有以下数组,其中包含值数组: $array = array( array('1', '2'), array('a', 'b', 'c'), array('x', 'y'), ); 可以有任意数量的数组,并且一个数组可以包含任意数量的值。我目前有一段代码,它将生成所有组合,其中每个数组中取一个值。例如: 1ax, 1ay, 1bx, 1by, 1cx, 1cy, 2ax, 2ay, 2bx, 2by, 2cx, 2cy 然而,我真正想要的只是每列中只有一个值的组合,即

我有以下数组,其中包含值数组:

$array = array(
    array('1', '2'),
    array('a', 'b', 'c'),
    array('x', 'y'),
);
可以有任意数量的数组,并且一个数组可以包含任意数量的值。我目前有一段代码,它将生成所有组合,其中每个数组中取一个值。例如:

1ax, 1ay, 1bx, 1by, 1cx, 1cy, 2ax, 2ay, 2bx, 2by, 2cx, 2cy
然而,我真正想要的只是每列中只有一个值的组合,即1ax不好,因为所有三个值1、a和x都位于第一列,1by不好,因为b和y位于第二列。因此,从上述示例中,只有这些组合是有效的:

1cy, 2cx
我最初计划只生成所有组合,然后过滤掉有冲突的组合,但这并不能扩展,因为这是一个过于简单的示例,在实际应用程序中,可能会有数百万个组合(包括冲突的组合)

有谁能提供更好的解决方法吗?我正在使用PHP,但是任何能够清楚地演示逻辑的代码示例都会很有帮助

提前谢谢


更新: 我已经针对更大的数据集测试了解决方案,以获得一些基准,目前的结果如下:

$array = array(
    array('1', '2', '3', '1', '2', '3', '1', '2', '3', '1', '2', '3', '1', '2', '3'),
    array('a', 'b', 'c', 'd', 'a', 'b', 'c', 'd', 'a', 'b', 'c', 'd', 'a', 'b', 'c', 'd', 'a', 'b', 'c', 'd'),
    array('x', 'y', 'z', 'x', 'y', 'z', 'x', 'y', 'z'),
    array('1', '2', '3', '1', '2', '3', '1', '2', '3'),
    array('a', 'b', 'c', 'd', 'a', 'b', 'c', 'd', 'a', 'b', 'c', 'd'),
    array('x', 'y', 'z'),
);
Josh Davis第二个解决方案:

Combinations:      249480
Time:              0.3180251121521 secs
Memory Usage:      22.012168884277 mb
Peak Memory Usage: 22.03059387207 mb
Combinations:      249480
Time:              1.1172790527344 secs
Memory Usage:      22.004837036133 mb
Peak Memory Usage: 22.017387390137 mb
Combinations:      249480
Time:              5.7098741531372 secs
Memory Usage:      39.145843505859 mb
Peak Memory Usage: 39.145843505859 mb
乔什·戴维斯:

Combinations:      249480
Time:              0.3180251121521 secs
Memory Usage:      22.012168884277 mb
Peak Memory Usage: 22.03059387207 mb
Combinations:      249480
Time:              1.1172790527344 secs
Memory Usage:      22.004837036133 mb
Peak Memory Usage: 22.017387390137 mb
Combinations:      249480
Time:              5.7098741531372 secs
Memory Usage:      39.145843505859 mb
Peak Memory Usage: 39.145843505859 mb
汤姆·黑格:

Combinations:      249480
Time:              0.3180251121521 secs
Memory Usage:      22.012168884277 mb
Peak Memory Usage: 22.03059387207 mb
Combinations:      249480
Time:              1.1172790527344 secs
Memory Usage:      22.004837036133 mb
Peak Memory Usage: 22.017387390137 mb
Combinations:      249480
Time:              5.7098741531372 secs
Memory Usage:      39.145843505859 mb
Peak Memory Usage: 39.145843505859 mb

也许这不是最优雅的方式,但它确实起到了作用 (javascript)

var结果=[];

对于(i=0;i这可以使用递归进行重构,使其能够处理任意数量的数组。如果我有时间,我会亲自尝试一下

另外,我不懂php,这个例子是用Delphi编写的

编辑:具有任意数组的递归解决方案

类型
TSingleArray=字符串数组;
TMasterArray=阵列的数组;
变量
解决方案:整数数组;//保存当前使用的单数组索引的Q&D容器
过程写解析(const masterArray:TMasterArray);
变量
I:整数;
索引:字符串;
解决方案:字符串;
开始
对于I:=0到高(解决方案)do
开始
索引:=索引+IntToStr(解决方案[I])+“”;
解决方案:=解决方案+主阵列[I][solutions[I]];
结束;
Writeln('Solution:'+Solution+'使用索引:'+index);
结束;
过程查找解决方案(常量主数组:TMasterArray;常量singleArrayIndex:整数;变量位:整数);
变量
I:整数;
开始
对于I:=0到高(主阵列[singleArrayIndex])do
开始
//*****使用位操作检查当前索引是否已在使用中
如果位和(1 shl I)=(1 shl I),则继续;
解[singleArrayIndex]:=I;
公司(bits,1 shl I);
//*****如果它不是主数组中的最后一个数组,则通过递归调用arrays继续。
如果singleArrayIndex高(masterArray),则查找解决方案(masterArray,Succ(singleArrayIndex),位)
else写入解决方案(主阵列);
Dec(位,1 shl I);
结束;
结束;
//***************
//初始化
//***************
变量
一、 J:整数;
位:整数;
singleArrayString:字符串;
主阵列:TMasterArray;
开始
I:=10;
设置长度(主阵列,I);
对于I:=0到高(主阵列)do
开始
设置长度(主阵列[I],高(主阵列)+成功(I));
singleArrayString:=EmptyStr;
对于J:=0到高(主阵列[I])do
开始
主阵列[I][J]:=IntToStr(J);
singleArrayString:=singleArrayString+masterArray[I][J];
结束;
WriteLn(单阵列字符串)
结束;
ReadLn;
//******开始使用递归解决问题
位:=0;
设置长度(解决方案,Succ(高(主阵列));
FindSolution(主阵列,0,位);
结束。

您的问题与的问题类似。在您的示例中,imho的最佳方法是使用一些符号(如“0”)填充较小的数组,以便它们都具有相同数量的值

$array = array(
    array('1', '2', '0'),
    array('a', 'b', 'c'),
    array('x', 'y', '0'),
);

然后循环遍历每个第一个数组值,对于每个增量,数组的索引为1,并检查下一个数组和下一列(在第一个循环中,它将为“1”,索引将为0递增-1,然后获取$array-'b',依此类推)如果达到“0”,则中断,如果达到右边框,则将第一个索引重置为0。然后执行相同的递减操作,您将获得所有组合。可能不清楚,请检查我链接到的图像。我认为这是可行的。它使用递归像树一样遍历结构。对于每个分支,它跟踪哪些列已被删除这可能是缓慢的,因为这是一个蛮力的方法

<?php 

$array = array(
    array('1', '2'),
    array('a', 'b', 'c'),
    array('x', 'y'),
);


function f($array, & $result, $colsToIgnore = array(), $currentPath = array()) {
    $row = array_shift($array);
    $length = count($row);
    $isMoreRows = !! count($array);

    for ($col = 0; $col < $length; $col++) {
        //check whether column has already been used
        if (in_array($col, $colsToIgnore)) {
            continue;   
        }

        $item = $row[$col];

        $tmpPath = $currentPath;
        $tmpPath[] = $item;

        if ($isMoreRows) {
            $tmpIgnoreCols = $colsToIgnore;
            $tmpIgnoreCols[] = $col;
            f($array, $result, $tmpIgnoreCols, $tmpPath);
        } else {
            $result[] = implode('', $tmpPath);
        }

    }
}


$result = array();
f($array, $result);
print_r($result);

有趣的问题!这比我想象的要复杂得多,但似乎有效

基本策略是先将数组从最小到最大排序(跟踪它们的顺序,以便以正确的顺序输出答案)

我以索引数组的形式将答案保存到输入列表的排序数组中

现在列表已排序,我可以将第一个正确答案存储为数组(0,1,2,…,n)

然后,我递归到一个函数中,通过将它与应答数组中的其他值(对于该插槽来说不是太大的所有值)交换,来尝试第一个插槽(上面的0)中的所有值。因为我已经按大小对它进行了排序,所以在交换时,我可以将任何值向右移动,而不用担心它对于那个正确的插槽来说太大

输出每个有效的槽有一些疯狂的间接操作来撤销所有排序

如果这看起来很混乱,很抱歉。我没有花太多时间清理它

<?php
# $lists is an array of arrays
function noconfcombos($lists) {
    $lengths = array();
    foreach($lists as $list) {
        $lengths[] = count($list);
    }

    # find one solution (and make sure there is one)
    $answer = array();
    $sorted_lengths = $lengths;
    asort($sorted_lengths);
    $answer_order_lists = array();
    $answer_order_lengths = array();
    $output_order = array();
    $min = 1;
    $max_list_length = 0;
    foreach($sorted_lengths as $lists_key => $list_max) {
        if($list_max < $min) {
            # no possible combos
            return array();
        }
        $answer[] = $min - 1; # min-1 is lowest possible value (handing out colums starting with smallest rows)
        $output_order[$lists_key] = $min - 1; # min-1 is which slot in $answers corresponds to this list
        $answer_order_lists[] = $lists[$lists_key];
        $answer_order_lengths[] = $lengths[$lists_key];
        ++$min;
    }
    ksort($output_order);
    $number_of_lists = count($lists);
    $max_list_length = end($sorted_lengths);
    if($max_list_length > $number_of_lists) {
       for($i = $number_of_lists; $i < $max_list_length; ++$i) {
          $answer[] = $i;
       }
       $stop_at = $number_of_lists;
    } else {
       $stop_at = $number_of_lists - 1;
    }

    # now $answer is valid (it has the keys into the arrays in $list for the
    # answer), and we can find the others by swapping around the values in
    # $answer.

    $ret = array();
    $ret[] = noconfcombos_convert($answer, $answer_order_lists, $output_order);
    noconfcombos_recurse($ret, $max_list_length, $stop_at, $answer_order_lengths, $answer_order_lists, $output_order, $answer, 0);

    return $ret;
}

# try swapping in different indexes into position $index, from the positions
# higher, then recurse
function noconfcombos_recurse(&$ret, $max_list_length, $stop_at, &$lengths, &$lists, &$output_order, $answer, $index) {
    if($index < $stop_at) {
        noconfcombos_recurse($ret, $max_list_length, $stop_at, $lengths, $lists, $output_order, $answer, $index + 1);
    }
    for($other = $index + 1; $other < $max_list_length; ++$other) {
        if($answer[$other] < $lengths[$index]) { # && $answer[$index] < $lengths[$other]) {
            $tmp = $answer[$index];
            $answer[$index] = $answer[$other];
            $answer[$other] = $tmp;
            $ret[] = noconfcombos_convert($answer, $lists, $output_order);
            if($index < $stop_at) {
                noconfcombos_recurse($ret, $max_list_length, $stop_at, $lengths, $lists, $output_order, $answer, $index + 1);
            }
        }
    }
}


function noconfcombos_convert(&$indexes, &$lists, &$order) {
    $ret = '';
    foreach($order as $i) {
        $ret .= $lists[$i][$indexes[$i]];
    }
    return $ret;
}

function noconfcombos_test() {
    $a = array('1', '2', '3', '4');
    $b = array('a', 'b', 'c', 'd', 'e');
    $c = array('x', 'y', 'z');
    $all = array($a, $b, $c);
    print_r(noconfcombos($all));
}

noconfcombos_test();

从不同的角度来看:为了组成一个结果行,你需要为每一列选取一个值。每个值都应该从不同的源行选取。这个问题被称为“从M中选取N”,或者更精确地说,从M中选取N

这意味着结果行对应于源行索引数组

您可以通过开始构建这样的索引数组(伪代码)来构建所有可能的选取

这就是其中之一
list($a0,$a1,$a2) = $array;
foreach ($a0 as $k0 => $v0)
{
    unset($a1[$k0], $a2[$k0]);
    foreach ($a2 as $k2 => $v2)
    {
        unset($a1[$k2]);
        foreach ($a1 as $k1 => $v1)
        {
            $result[] = "$v0$v1$v2";
        }
        $a1[$k2] = $array[1][$k2];
    }
    $a1[$k0] = $array[1][$k0];
    $a2[$k0] = $array[2][$k0];
}
$keys = array_map('count', $array);
arsort($keys);

$inner_keys = array();
foreach ($keys as $k => $cnt)
{
    $keys[$k] = $inner_keys;
    $inner_keys[] = $k;
}

$result = array();

$php = 'list($a' . implode(',$a', array_keys($array)) . ')=$array;';
foreach (array_reverse($keys, true) as $i => $inner_keys)
{
    $php .= 'foreach ($a' . $i . ' as $k' . $i . ' => $v' . $i . '){';

    if ($inner_keys)
    {
        $php .= 'unset($a' . implode('[$k' . $i . '],$a', $inner_keys) . '[$k' . $i . ']);';
    }
}

$php .= '$result[] = "$v' . implode('$v', array_keys($array)) . '";';

foreach ($keys as $i => $inner_keys)
{
    foreach ($inner_keys as $j)
    {
        $php .= '$a' . $j . '[$k' . $i . ']=$array[' . $j . '][$k' . $i . "];\n";
    }
    $php .= '}';
}
eval($php);
    $arr = array(
        array('1', '2'),
        array('a', 'b', 'c'),
        array('x', 'y'),
    );
    //-----
    //assuming $arr consists of non empty sub-arrays
    function array_combinations($arr){ 
        $max = 1;
        for ($i = 0; $i < count($arr); $i ++){
            $max *= count($arr[$i]); 
        }
        $matrix = array();
        for ($i = 0; $i < $max; $i ++){
            $matrix = array(); 
        }
        $c_rep = 1;
        for ($i = count($arr) - 1; $i >= 0; $i --){
            $c_rep *= ($i < count($arr) - 1)//last sub-array 
                ? count($arr[$i + 1])
                : 1;
            $k = 0; 
            while ($k < $max){
                for ($t = 0; $t < count($arr[$i]); $t ++){
                    for ($j = 0; $j < $c_rep; $j ++){
                        $matrix[$i][$k ++] = $arr[$i][$t];
                    }
                }   
            }
        }
        return $matrix;
    }
    //-----
    $matrix = array_combinations($arr);
function algorithmToCalculateCombinations($n, $elems) {
        if ($n > 0) {
            $tmp_set = array();
            $res = algorithmToCalculateCombinations($n - 1, $elems);
            foreach ($res as $ce) {
                foreach ($elems as $e) {
                    array_push($tmp_set, $ce . $e);
                }
            }
            return $tmp_set;
        } else {
            return array('');
        }
    }

$Elemen = array(range(0,9),range('a','z'));
$Length = 3;
$combinations = algorithmToCalculateCombinations($Length, $Elemen);