小编典典

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

algorithm

我有以下包含值数组的数组:

$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

乔什·戴维斯(Josh Davis):

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

阅读 342

收藏
2020-07-28

共1个答案

小编典典

这是自生成代码和蛮力在简单性和性能方面击败大多数算法的情况之一。在先前的答案中,我已经看到了很多递归,数组操作和计算,而实际上您想要做的是:

foreach ($array[0] as $k0 => $v0)
{
    foreach ($array[1] as $k1 => $v1)
    {
        if ($k1 == $k0)
        {
            continue;
        }
        foreach ($array[2] as $k2 => $v2)
        {
            if ($k2 == $k1 || $k2 == $k0)
            {
                continue;
            }
            $result[] = $v0.$v1.$v2;
        }
    }
}

当然,除非您知道其中有多少个数组,否则您无法编写此代码$array。这就是生成的代码很方便的地方:

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

$php = '';
foreach ($array as $i => $arr)
{
    $php .= 'foreach ($array[' . $i . '] as $k' . $i . ' => $v' . $i . '){';

    if ($i > 0)
    {
        $sep  = 'if (';
        $j    = $i - 1;
        do
        {
            $php .= $sep . '$k' . $i . ' == $k' . $j;
            $sep  = ' || ';
        }
        while (--$j >= 0);

        $php .= ') { continue; } ';
    }
}

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

eval($php);
print_r($result);

请注意,此例程假定它$array是从零开始的数字索引数组,如您的示例所示。它将生成上面引用的代码,并针对任意数量的数组进行了调整。


更新资料

这是另一种算法。它仍然是自生成的,但暴力程度较低。我们仍然有嵌套循环,除了每个循环都在数组的副本上工作外,其中外部循环当前使用的键已从该循环的数组中删除。例如,如果值应为(a,b,c),但外部循环使用索引0和2,则删除“
a”(索引0)和“ c”(索引2),剩下的就是“ b”。这意味着循环仅在可能的组合上起作用,我们不再需要任何if条件。

此外,这部分也可以应用于先前的算法,我们按从最小到最大的顺序处理值的数组,以确保在当前数组中存在使用过的索引。缺点是它不会以相同的顺序生成组合。它生成相同的组合,只是顺序不同。代码如下:

$a0 = $array[0];
foreach ($a0 as $k0 => $v0)
{
    $a2 = $array[2];
    unset($a2[$k0]);
    foreach ($a2 as $k2 => $v2)
    {
        $a1 = $array[1];
        unset($a1[$k0], $a1[$k2]);
        foreach ($a1 as $k1 => $v1)
        {
            $result[] = "$v0$v1$v2";
        }
    }
}

上面的例程在每个循环的开始处设置值的副本,然后删除外部循环使用的值。您可以通过在开始时 只设置一次
值的副本,在使用时删除键(在每个循环的开头)并在释放键时将它们放回(在每个循环的结尾)来改进此过程。 。该例程如下所示:

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);
2020-07-28