PHP:优化数组迭代

时间:2016-12-28 12:44:44

标签: php optimization multidimensional-array nested-loops

我正在开发一种基于最高分数对队伍进行排序的算法。球队将从球员名单中生成。创建团队的条件是

  • 应该有6名球员。
  • 6名队员的集体薪水必须小于或等于50K。
  • 团队将根据最高集体投射生成。

我为获得这个结果所做的是生成团队的所有可能性然后对它们进行检查以排除那些薪水超过50K的团队,然后根据投影对剩余部分进行排序。但是生成所有可能性需要花费大量时间,有时它会占用所有内存。对于160名玩家的列表,大约需要90秒。这是代码

$base_array = array();
$query1 = mysqli_query($conn, "SELECT * FROM temp_players  ORDER BY projection DESC");
while($row1 = mysqli_fetch_array($query1))
{
    $player = array();
    $mma_id = $row1['mma_player_id'];
    $salary = $row1['salary'];
    $projection = $row1['projection'];
    $wclass = $row1['wclass'];

    array_push($player, $mma_id);
    array_push($player, $salary);
    array_push($player, $projection);
    array_push($player, $wclass);

    array_push($base_array, $player);
}


$result_base_array = array();
$totalsalary = 0;

for($i=0; $i<count($base_array)-5; $i++)
{
    for($j=$i+1; $j<count($base_array)-4; $j++)
    {
        for($k=$j+1; $k<count($base_array)-3; $k++)
        {
            for($l=$k+1; $l<count($base_array)-2; $l++)
            {
                for($m=$l+1; $m<count($base_array)-1; $m++)
                {
                    for($n=$m+1; $n<count($base_array)-0; $n++)
                    {
                        $totalsalary = $base_array[$i][1]+$base_array[$j][1]+$base_array[$k][1]+$base_array[$l][1]+$base_array[$m][1]+$base_array[$n][1];
                        $totalprojection = $base_array[$i][2]+$base_array[$j][2]+$base_array[$k][2]+$base_array[$l][2]+$base_array[$m][2]+$base_array[$n][2];
                        if($totalsalary <= 50000)
                        {
                            array_push($result_base_array, 
                            array($base_array[$i], $base_array[$j], $base_array[$k], $base_array[$l], $base_array[$m], $base_array[$n],
                            $totalprojection, $totalsalary)
                            );

                        }
                    }
                }
            }
        }

    }
}

usort($result_base_array, "cmp");

和cmp功能

function cmp($a, $b) {
    if ($a[6] == $b[6]) {
        return 0;
    }
    return ($a[6] < $b[6]) ? 1 : -1;
}

是否有减少执行此任务所需的时间,或任何其他解决方法来获得所需数量的团队

此致

2 个答案:

答案 0 :(得分:0)

由于数组中的元素数量可能非常大(例如,100个玩家可以生成1.2 * 10 ^ 9个团队),因此无法将其保存在内存中。尝试将结果数组保存到文件中(每次保存后截断数组)。然后使用外部文件排序。

它会很慢,但至少它不会因记忆而下降。

如果您需要前n个团队(例如10个具有最高预测的团队),那么您应该将生成result_base_array的代码转换为生成器,因此它将yeld下一个团队而不是将其推送到数组中。然后迭代这个生成器。在每次迭代中,将新项添加到已排序的结果数组并剪切冗余元素。

答案 1 :(得分:0)

根据工资是否经常被排除,您也可以在其他循环中对此进行测试。如果在4名球员选择之后他们的总薪水已经超过50K,那么选择剩余的2名球员是没有用的。这可以为您节省一些迭代次数。

通过记住包中最低的6个工资可以进一步改善,然后检查选择4个成员后,如果要添加现有的2个最低工资,您仍然会保持在50K以下。如果这是不可能的,那么再次尝试添加剩下的两个玩家是没有用的。当然,这可以在选择的每个阶段完成(选择1名球员,2名球员,......)

当您按升薪对数据进行排序时,另一项相关改进会发挥作用。如果在选择第4个玩家之后,上面的逻辑会让你得出结论,你不能再增加2个玩家就不能保持在50K以下,那么就没有必要用数据系列中的下一个替换第4个玩家:该玩家将有一个更高的工资,所以它也会产生超过50K的总和。这意味着你可以立即回溯并进行第三个选手的选择。

正如其他人所指出的那样,潜在解决方案的数量是巨大的。对于160个团队和6个成员的团队,组合数量为:

160 . 159 . 158 . 157 . 156 . 155
--------------------------------- = 21 193 254 160
        6 . 5 . 4 . 3 . 2

210亿条记录是一段记忆,也可能对你没用:你真的对4 432 456 911 th 地方的团队感兴趣吗?

你可能会对这些团队的前10名(投影方面)感兴趣。这可以通过保留10个最佳团队的列表来实现,然后,当您获得具有可接受薪水的新团队时,将其添加到该列表中,保持其排序(通过二分搜索),并弹出条目从那个前10名的最低投影。

以下是您可以使用的代码:

$base_array = array();
// Order by salary, ascending, and only select what you need
$query1 = mysqli_query($conn, "
     SELECT   mma_player_id, salary, projection, wclass 
     FROM     temp_players 
     ORDER BY salary ASC");
// Specify with option argument that you only need the associative keys:
while($row1 = mysqli_fetch_array($query1, MYSQLI_ASSOC)) {
    // Keep the named keys, it makes interpreting the data easier:
    $base_array[] = $row1;
}

function combinations($base_array, $salary_limit, $team_size) {
    // Get lowest salaries, so we know the least value that still needs to
    // be added when composing a team. This will allow an early exit when
    // the cumulative salary is already too great to stay under the limit.
    $remaining_salary = [];
    foreach ($base_array as $i => $row) {
        if ($i == $team_size) break;
        array_unshift($remaining_salary, $salary_limit);
        $salary_limit -= $row['salary'];
    }

    $result = [];
    $stack = [0];
    $sum_salary = [0];
    $sum_projection = [0];
    $index = 0;
    while (true) {
        $player = $base_array[$stack[$index]];
        if ($sum_salary[$index] + $player['salary'] <= $remaining_salary[$index]) {
            $result[$index] = $player;
            if ($index == $team_size - 1) {
                // Use yield so we don't need to build an enormous result array:
                yield [
                    "total_salary" => $sum_salary[$index] + $player['salary'],
                    "total_projection" => $sum_projection[$index] + $player['projection'],
                    "members" => $result
                ];
            } else {
                $index++;
                $sum_salary[$index] = $sum_salary[$index-1] + $player['salary'];
                $sum_projection[$index] = $sum_projection[$index-1] + $player['projection'];
                $stack[$index] = $stack[$index-1];
            }
        } else {
            $index--;
        }
        while (true) {
            if ($index < 0) {
                return; // all done
            }
            $stack[$index]++;
            if ($stack[$index] <= count($base_array) - $team_size + $index) break;
            $index--;
        }
    }
}

// Helper function to quickly find where to insert a value in an ordered list
function binary_search($needle, $haystack) {
    $high = count($haystack)-1;
    $low = 0;
    while ($high >= $low) {
        $mid = (int)floor(($high + $low) / 2);
        $val = $haystack[$mid];
        if ($needle < $val) {
            $high = $mid - 1;
        } elseif ($needle > $val) {
            $low = $mid + 1;
        } else {
            return $mid;
        }
    }
    return $low;
}

$top_team_count = 10; // set this to the desired size of the output
$top_teams = []; // this will be the output
$top_projections = [];
foreach(combinations($base_array, 50000, 6) as $team) {
    $j = binary_search($team['total_projection'], $top_projections);
    array_splice($top_teams, $j, 0, [$team]);
    array_splice($top_projections, $j, 0, [$team['total_projection']]);
    if (count($top_teams) > $top_team_count) {
        // forget about lowest projection, to keep memory usage low
        array_shift($top_teams);
        array_shift($top_projections);
    }
}
$top_teams = array_reverse($top_teams); // Put highest projection first

print_r($top_teams);

查看demo on eval.in,它只会生成12名随机薪资和投影数据的玩家。

最后的评论

即使有上述优化,为160个团队执行此操作可能仍需要大量迭代。工资越多,工资超过50K,表现越好。如果这种情况从未发生过,那么算法就无法避免必须查看210亿个组合中的每一个。如果您事先知道 ,那么50K限制将不起任何作用,您当然会按照原来的预测顺序对数据进行排序。

另一种优化方法可能是,如果您要将组合功能反馈到目前为止第10个最高的团队预测。然后,该功能可以消除导致总投影较低的组合。您可以先获取6个最高玩家投影值,并使用此值来确定部分团队投影在增加缺失玩家时仍能增长多少。选择几个玩家后,这可能会变得不可能,然后你可以跳过一些迭代,就像在薪水的基础上做的那样。