多个foreach超过3700万种可能性

时间:2012-02-16 11:38:15

标签: php arrays

我的任务是使用8个区块中的数据创建所有可能性的列表。

8个街区有以下几种可能性:

*Block 1: 12 possibilities
*Block 2: 8 possibilities
*Block 3: 8 possibilities
*Block 4: 11 possibilities
*Block 5: 16 possibilities
*Block 6: 11 possibilities
*Block 7: 5 possibilities
*Block 8: 5 possibilities

这可能有37,171,200种可能性。

我只是尝试并限制只显示返回的值,使用正确的字符串长度,如下所示:

foreach($block1 AS $b1){
    foreach($block2 AS $b2){
        foreach($block3 AS $b3){
            foreach($block4 AS $b4){
                foreach($block5 AS $b5){
                    foreach($block6 AS $b6){
                        foreach($block7 AS $b7){
                            foreach($block8 AS $b8){
                                if (strlen($b1.$b2.$b3.$b4.$b5.$b6.$b7.$b8) == 16)
                                {
                                    echo $b1.$b2.$b3.$b4.$b5.$b6.$b7.$b8.'<br/>';
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

然而,执行时间太长而无法计算。我想知道是否有人知道更简单的方法吗?

4 个答案:

答案 0 :(得分:3)

您可以尝试将预先计算的部分存储在前面每个lelel中已知的连接字符串中,以便以后重用,避免连接最内层循环中的所有内容

foreach($block7 AS $b7){
    $precomputed7 = $precomputed6.$b7
    foreach($block8 AS $b8){
        $precomputed8 = $precomputed7.$b8
        if (strlen($precomputed8) == 16) {
            echo $precomputed8.'<br/>';
        }
    }
}

类似于先例水平。然后你可以尝试在更高的循环级别之一测试已经更长为16个字符的字符串。您可以快捷方式,避免尝试其他可能性。但要注意计算字符串的长度会花费很多性能,也许后者的改进根本不值得,具体取决于输入数据。

另一个想法是预先计算每个块的长度,然后递归长度数组,计算总和应该比连接和计算字符串长度更快。对于匹配长度为16的索引Vector,您可以轻松输出完整的连接字符串。

答案 1 :(得分:3)

您可以通过缓存字符串前缀并记住它们的长度来改进算法。然后你不必为每个组合做到这一点。

$len = 16:

// array for remaining characters per level
$r = array($len);
// array of level parts
$p = array();
foreach ($block1 AS &$b1) {
    // skip if already too long
    if (($r[0] - strlen($b1)) <= 0) continue;
    $r[1] = $r[0] - strlen($b1);
    foreach ($block2 AS &$b2) {
        if (($r[1] - strlen($b2)) <= 0) continue;
        $r[2] = $r[1] - strlen($b2);
        foreach ($block3 AS $b3) {
            // …
            foreach ($block8 AS &$b8) {
                $r[8] = $r[7] - strlen($b8);
                if ($r[8] == 0) {
                    echo implode('', $p).'<br/>';
                }
            }
        }
    }
}

此外,使用foreach中的引用将在内部使用数组副本停止PHP。

答案 2 :(得分:2)

由于您的长度要求为16,并且假设每个(i)八个块中每个(b)的可能性具有长度x_i_b,您可以通过某些情况得到一些减少。

例如,假设我们有长度要求16,但只有4个块,可能有长度指示

第1块:[2,3,4] 第2块:[5,6,7] 第3块:[8,9,10] 第4块:[9,10,11]

然后所有可能性都是不可能的,因为块4的长度都太大而不允许构成16的其余部分的块1-3的任何组合。

现在,如果你的长度真的是16,则意味着你的(可能的)长度范围从1到9,假设没有0长度。

我可以看到两种方法来解决这个问题:

  1. 贪婪
  2. 动态编程
  3. 甚至可能将它们结合起来。对于贪婪的方法,选择所有区块中最大的可能性,然后是下一个最大的区域,直到你越过16的门槛。如果你得到了所有的区块,那么你可以发射那个区块。

    无论你是否达到了门槛,你都可以迭代这些可能性。

    动态appraoch意味着您应该存储已经计算过的一些结果。就像从一些块中选择一个长度为7的块,你不需要在将来重新计算它,但你可以遍历其余的块,看看你是否能找到一个组合来给你9。 / p>

    编辑:这有点像背包问题,但每个实例每个块有1个选择的附加限制。无论如何,就其他优化而言,绝对只需将块处理成长度数组,并在每个迭代级别保持运行总和。因此,每次循环每次迭代只需要1次求和,而不是每次迭代得到8次求和。如果需要发出选择,也只有str concat。

    如果您不想要一般解决方案(如果不这样做可能会更容易),那么您可以通过排除最大的太小的长度组合(以及所有小于此的选择)来手动编写很多问题实例特定的加速代码,排除最小的太大的长度组合(以及所有选择的更大)。

答案 3 :(得分:1)

如果您可以将其表达为嵌套数组,请尝试使用RecursiveIteratorIterator,http://php.net/manual/en/class.recursiveiteratoriterator.php