如何改进我的PHP代码以提高效率?

时间:2009-12-10 16:01:16

标签: php performance

我编写了一个简单的PHP脚本,用于检查随机值是否为有效的Rugby Union分数。它工作得很好,但效率不高,任何改进它的建议都会受到欢迎。

$score = rand(0, 60);

/* Rugby Union
 * 
 * Try = 5 points
 * Conversion = 2 points
 * Penalty = 3 points
 * Drop goal = 3 points
 * 
 */

echo "<h1>Score: ".$score."</h1>";

for ($tries = 0; $tries <= 12; $tries++)
{
    for ($conversions = 0; $conversions <= 30; $conversions++)
    {
        for ($dropgoals = 0; $dropgoals <= 20; $dropgoals++)
        {
            if ($conversions > $tries)
            {
                //echo "<br />Illegal score";
            }
            else
            {
                $testscore = ($tries * 5) + ($conversions * 2) + ($dropgoals * 3);
                if ($testscore == $score)
                {
                    if ($dropgoals == 0)
                    {
                        echo "Found a way to achieve score with ".$tries." tries ".$conversions." conversions and ".$dropgoals." drop goals.<br />";
                    }
                    else
                    {
                        echo "Found a way to achieve score with ".$tries." tries ".$conversions." conversions and ".$dropgoals." drop goals or penalties.<br />";
                    }
                }
            }
        }
    }
}

好的,这是修改后的解决方案,如果可能的话,减少嵌套for循环的数量会很好......

echo "<h1>Score: ".$score."</h1>";

for ($tries = 0; $tries <= 12; $tries++) {
    for ($conversions = 0; $conversions <= $tries; $conversions++) {
        for ($dropgoals = 0; $dropgoals <= 20; $dropgoals++){
            if ($conversions <= $tries) {
                    $testscore = ($tries * 5) + ($conversions * 2) + ($dropgoals * 3);
                    if ($testscore == $score) {
                        echo "Found a way to achieve score with ".$tries." tries ".$conversions." conversions and ".$dropgoals.($dropgoals == 0 ? " drop goals.<br />" : " drop goals or penalties.<br />");
                    }
            }
        }
    }
}

11 个答案:

答案 0 :(得分:2)

好好开始

for ($conversions = 0; $conversions <= 30; $conversions++)

可以改为

for ($conversions = 0; $conversions <= $tries; $conversions++)

答案 1 :(得分:2)

实际上,只有有限数量的可能分数,对吧?一个快速谷歌显示该记录是164点。那么为什么不一次生成每个可能得分的列表,最多可达到一定的最大值(300?500?),并在您的应用中对其进行硬编码。然后,在运行时,只需检查提供的分数是否在列表中。我认为到目前为止,这将是最有效的解决方案。

编辑:如果您还想输出尝试次数,惩罚次数和放弃目标,此方法仍然有效 - 只需生成这些值 - 一次 - 并将它们保留在列表中同样(作为二维数组或关联数组)。

答案 2 :(得分:1)

当您说“高效”时,请说明您的意思。

代码执行得太慢了吗?它现在运行的速度有多快,你需要多快才能运行?如果你不能定义“不够快”,那么你没有目标。

在你猜测要加速什么之前,在此之前所有的受访者都鼓励你做出散射加速,这是一种伤害,你需要分析你的代码,看看大部分时间花在哪里。

答案 3 :(得分:1)

你的if else函数应该被反转。将最可能的场景设为if(){}子句,以及else {}

中的异常错误
if ($conversions < $tries)
                        {
                                $testscore = ($tries * 5) + ($conversions * 2) + ($dropgoals * 3);
                                if ($testscore == $score)
                                {
                                        if ($dropgoals == 0)
                                        {
                                                echo "Found a way to achieve score with ".$tries." tries ".$conversions." conversions and ".$dropgoals." drop goals.<br />";
                                        }
                                        else
                                        {
                                                echo "Found a way to achieve score with ".$tries." tries ".$conversions." conversions and ".$dropgoals." drop goals or penalties.<br />";
                                        }
                                }
                        }
                        else
                        {
                              // echo "illegal score";
                        }

答案 4 :(得分:1)

这样可以清理一下。

echo "Found a way to achieve score with ".$tries." tries ".$conversions." conversions and ".$dropgoals.($dropgoals == 0 ? " drop goals.<br />" : " drop goals or penalties.<br />");

答案 5 :(得分:0)

稍加修改的版本。我希望我更了解橄榄球。

$score = rand(0, 60);

/* Rugby Union
 * 
 * Try = 5 points
 * Conversion = 2 points
 * Penalty = 3 points
 * Drop goal = 3 points
 * 
 */

echo "<h1>Score: ".$score."</h1>";

for ($tries = 0; $tries <= 12; $tries++){
    for ($conversions = 0; $conversions <= $tries; $conversions++){
        for ($dropgoals = 0; $dropgoals <= 20; $dropgoals++){
            else{
                $testscore = ($tries * 5) + ($conversions * 2) + ($dropgoals * 3);
                if ($testscore == $score){
                    if ($dropgoals == 0){
                        echo "Found a way to achieve score with ".$tries." tries ".$conversions." conversions and ".$dropgoals." drop goals.<br />";
                    }
                    else{
                        echo "Found a way to achieve score with ".$tries." tries ".$conversions." conversions and ".$dropgoals." drop goals or penalties.<br />";
                    }
                }
            }
        }
    }
    if ($conversions > $tries){
        echo "<br />Illegal score";
    }
}

答案 6 :(得分:0)

当你找到解决方案时,你也可以摆脱循环:

if ($testscore == $score) 
{
  echo "Found a way to achieve ....";
  break 3;
}

该数字指定了要突破的循环数,因此在撰写本文时,有3个for循环要退出。

答案 7 :(得分:0)

是的,3个foor循环对于这样的问题似乎太多了。但是,当您想要找到n = x*5 + y*3 + z*2x>=z的组合x,y,z时,我认为没有更简单的解决方案。但是,您可以减少迭代次数 如果你想获得所有可能的组合,或者只是回答“是的,这是一个有效的分数”,那将是一件好事。

无论如何,这是我的建议:

$score = rand(0, 60);

/* Rugby Union
 * 
 * Try = 5 points
 * Conversion = 2 points
 * Penalty = 3 points
 * Drop goal = 3 points
 * 
 */

// compute how often the points fit into the score
$maxTries = intval($score / 5);
$maxConversions = min(intval($score / 2),$maxTries);
$maxDrops = intval($score / 3);

$valid = false;
for ($tries = 0; $tries <= $maxTries; $tries++)
{   
    // this way, you avoid recomputing the value over and over again in the third loop 
    $scoreTries = $tries * 5;
    for ($conversions = 0; $conversions <= $maxConversions; $conversions++)
    {
        $scoreCons = $scoreTries  + $conversions * 2;
        for ($dropgoals = 0; $dropgoals <= $maxDrops; $dropgoals++)
        {
            $scoreTotal = $scoreCons + $dropgoals * 3
            if ($scoreTotal == $score)
            {
               echo 'Found a way to achieve score with '.$tries.' tries '.$conversions.' conversions and '.$dropgoals.' drop goals or penalties.<br />';
               $valid = true;
               // write 'break 3' here if you are satisfied with one answer                
            }
        }
    }
}

if (!$valid){
    echo "<br />Illegal score";
}

我不知道效率提高了多少,但一般来说,如果使用'dot'语法连接它们,将字符串括在单引号('string')中总是好的。这样,PHP就不会对变量的字符串进行求值来代替,在我看来,这是一种更清晰的方法。

编辑:

哦,我不区分$dropgoals == 0,因为在逻辑上没有区别。 ...and 0 drop goals....and 0 drop goals or penalties.

答案 8 :(得分:0)

同样的问题是找出给定数量的变化如何由给定的一组面额中的硬币组成。这是PHP中的一个实现。我怀疑它是否非常高效,但它比嵌套循环版本更通用。

<?php

f(
  30, // points scored in match
  array( // rugby union scoring structure
    "Converted Tries" => 7,
    "Unconverted Tries" => 5,
    "Drop Goals/Penalties" => 3,
  )
);

function f($points_left, $scoring_structure, $scores_so_far = array()){
  if($points_left==0){
    print_score($scores_so_far);
  }else if($points_left>0){
    if($scoring_structure){
      list($score_type, $points_for_score_type) =
        first_element($scoring_structure);

      // Option 1: Use a highest-denomination coin,
      // and make change for the rest.
      if($points_for_score_type <= $points_left){
        f(
          $points_left-$points_for_score_type,
          $scoring_structure,
          increment($scores_so_far,$score_type)
        );  
      }

      // Option 2: Attempt to make change for the full amount without
      // using the highest denomination coin at all.
      f(
        $points_left,
        all_except_first_element($scoring_structure),
        $scores_so_far
      );
    }
  }else{
    exit("Error: Should never reach here!\n");
  }
}

function increment($arr, $key){
 $arr[$key]++;
 return $arr;
}

function all_except_first_element($arr){
  list($k, $v) = first_element($arr);
  unset($arr[$k]);
  return $arr;
}

function first_element($arr){
  foreach($arr as $k=>$v){
    return array($k, $v);
  }
}

function print_score($scores_so_far){
  $parts = array();
  foreach($scores_so_far as $k=>$v){
    $parts[]= "$k: $v";
  }
  echo implode(", ", $parts), "\n";
}

答案 9 :(得分:0)

您的方法可以进一步改进 - 而不是查看三个变量的所有组合 - 尝试,转化和付款,您只能查看那些不超过$score的组合。虽然您仍然具有嵌套循环,但循环中执行代码的次数会减少。见下文。

echo "<h1>Score: ".$score."</h1>";

$triesScore = 5;
$conversionsScore = 2;
$dropgoalsScore = 3;

for ($tries = 0; $tries <= $score/$triesScore; $tries++) {
    for ($conversions = 0; $conversions <= ($score-$triesScore*$tries)/$conversionsScore; $conversions++) {
        for ($dropgoals = 0; $dropgoals <= ($score-$triesScore*$tries-$conversionsScore*$conversions)/$dropgoalsScore; $dropgoals++){
            $testscore = ($tries * $triesScore) + ($conversions * $conversionsScore) + ($dropgoals * $dropgoalsScore);
            if ($testscore == $score) {
                echo "Found a way to achieve score with ".$tries." tries ".$conversions." conversions and ".$dropgoals.($dropgoals == 0 ? " drop goals.<br />" : " drop goals or penalties.<br />");
            }
        }
    }
}

虽然实际上,最高分为60分,但改进非常小,可能无法察觉。

答案 10 :(得分:0)

预计算!我以为我怀疑性能会杀死你的应用,但为了以防万一。有1911种可能的组合和141种有效分数,因此您可以轻松预先计算数据,并将其存储在磁盘上并在需要时加载。

预先计算:

$scores = array();
for ($tries = 0; $tries <= 12; $tries++) {
    for ($conversions = 0; $conversions <= $tries; $conversions++) {
        for ($dropgoals = 0; $dropgoals <= 20; $dropgoals++){
            $score = ($tries * 5) + ($conversions * 2) + ($dropgoals * 3);
            if( !array_key_exists($score,$scores) ) $scores[$score] = array();
            $scores[$score][] = array( $tries, $conversions, $dropgoals );
        }
    }
}

echo "number of unique possible scores is " . count($scores) . "\n";
$number_combinations = 0;
foreach( $scores as $score => $combinations ) {
    echo "Score " . $score . " has " . count($combinations) . " combinations\n";
    $number_combinations += count($combinations);
}
echo "number of unique combinations is " . $number_combinations . "\n";

// store
file_put_contents("scores.txt",serialize($scores));

查找

$scores=unserialize(file_get_contents("scores.txt"))
$number_of_combinations_for_score_23 = array_key_exists(23,$scores) ? count($scores[23]) : 0;

你甚至可以减少得分数组在右侧只包含“有效或无效”的布尔值。这节省了一点查找时间和空间。