简体   繁体   English

如何改善PHP代码以使其更高效?

[英]How can I improve my PHP code to make it more efficient?

I've written a simple PHP script which checks if a random value is a valid Rugby Union score. 我编写了一个简单的PHP脚本,该脚本检查随机值是否为有效的橄榄球联合得分。 It works quite nicely but isn't particularly efficient, any advice on improving it would be most welcome. 它工作得很好,但效率不是特别高,因此欢迎提供任何改进建议。

$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 />";
                    }
                }
            }
        }
    }
}

Okay, here's the revised solution as it stands, it'd be nice to reduce the amount of nested for loops if possible... 好的,这是目前的修订解决方案,如果可能的话,减少嵌套的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 />");
                    }
            }
        }
    }
}

Well for a start 好吧开始

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

can change to 可以更改为

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

Practically speaking, there are only a finite number of possible scores, right? 实际上,只有可能的分数是有限的,对吧? A quick Google shows that the record is 164 points. 快速的Google显示记录为164点。 So why not generate, one time, a list of every possible score, up to a certain max (300? 500?), and hard-code it in your app. 因此,为什么不一次生成每个可能得分的列表,直到最大分数(300?500?),并在您的应用中对其进行硬编码。 Then, at runtime, just check if the supplied score is in the list. 然后,在运行时,只需检查提供的分数是否在列表中。 I think that will be, by far, the most efficient solution. 我认为,到目前为止,这将是最有效的解决方案。

Edit: This method will still work if you also want to output tries, penalties, and drop goals--just generate those values, too--one time--and keep them in the list as well (as a two-dimensional array or an associative array). 编辑:如果您还想输出尝试次数,罚分次数和掉球目标-只需一次生成这些值,并将它们也保留在列表中(作为二维数组或关联数组)。

When you say "efficient", please define what you mean. 当您说“有效”时,请定义您的意思。

Is the code executing too slowly? 代码执行太慢吗? How fast is it running now, and how fast do you need it to run? 现在它运行速度有多快,您需要它运行多快? If you can't define "not fast enough" then you don't have a goal. 如果您不能定义“不够快”,那么您就没有目标。

Before you go guessing at what to speed up, and before all the respondents here do you a disservice by encouraging you to make scattershot speedups, you need to profile your code to see where most of the time is being spent. 在您猜出要加速什么之前,在这里所有的受访者都在鼓励您加快散布速度对您的伤害之前,您需要分析代码以了解大部分时间在哪里。

your if else function should be inverted. 您的if else函数应该反转。 Make the most probable scenario comme into the if(){} clause, and the exceptional error in the 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";
                        }

这样可以清理一些东西。

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

A slightly modified version. 稍作修改的版本。 I wish I knew more about rugby. 我希望我对橄榄球有所了解。

$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";
    }
}

You can also break out of the loops when you've found a solution: 找到解决方案后,您还可以跳出循环:

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

The number specifies the number of loops to break out of, so at time of writing there were 3 for loops to exit. 数指明循环的数量打破了,所以在写作时有3 for循环退出。

Yeah 3 foor loops seem to be too much for such a problem. 是的,对于这样的问题,3次循环似乎太多了。 But as you want to find a combination x,y,z, such that n = x*5 + y*3 + z*2 with x>=z , I don't think there is a simpler solution. 但是,当您想找到x,y,z的组合,使得n = x*5 + y*3 + z*2x>=z ,我认为没有简单的解决方案。 You can however reduce the number of iterations. 但是,您可以减少迭代次数。
And it would be good to know if you want to get all possible combinations or just an answer like 'Yes, this is a valid score'. 最好知道是要获得所有可能的组合还是只是想回答“是的,这是有效分数”。

Anyway, this is my suggestions: 无论如何,这是我的建议:

$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";
}

I don't know how much the efficiency improves but in general it is always good to enclose strings in single quotes ('string') if you use the 'dot' syntax to connect them. 我不知道效率会提高多少,但是总的来说,如果使用“点”语法将字符串括在单引号(“字符串”)中总是很好的。 This way, PHP does not evaluate the strings for variables to substitute which is, in my opinion, a cleaner approach. 这样,PHP不会评估字符串以替换变量,我认为这是一种更干净的方法。

Edit: 编辑:

Oh and I don't distinguish between $dropgoals == 0 , because there is logically no differences between eg ...and 0 drop goals. 哦,我不区分$dropgoals == 0 ,因为从逻辑上说,例如...and 0 drop goals.之间没有区别...and 0 drop goals. and ...and 0 drop goals or penalties. ...and 0 drop goals or penalties.

It's the same problem as finding out how change for a given amount can be made up of coins from a given set of denominations. 这与找出如何用给定面额的硬币组成零钱是一个同样的问题。 Here's an implementation in PHP. 这是PHP的实现。 I doubt if it's very efficient, but it's more general than the nested loop version. 我怀疑它是否非常有效,但是比嵌套循环版本更通用。

<?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";
}

Your approach can be improved further - instead of looking through all combinations of the three variables - tries, conversions and dropgoals, you can only look at those combinations that do not exceed $score . 您的方法可以进一步改进-而不是查看三个变量的所有组合-尝试,转换和dropgoals,您只能查看不超过$score那些组合。 Though you still have the nested loops, the number of times the code inside the loop is executed is decreased. 尽管您仍然具有嵌套循环,但是循环内代码的执行次数减少了。 See below. 见下文。

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 />");
            }
        }
    }
}

Though in reality, for a max-score of 60, the improvements are very small, that they may be unnoticeable. 尽管实际上,最大得分为60,但改进很小,因此可能不会引起注意。

Precompute! 预计算! Thought I doubt that the performance kills your app, but just in case. 以为我怀疑性能会杀死您的应用程序,但以防万一。 There are 1911 possible combinations, and 141 valid scores, so you can easily precompute the data, and store it on disk and load it whenever required. 共有1911种可能的组合和141个有效分数,因此您可以轻松地预先计算数据,并将其存储在磁盘上并在需要时加载它。

Precomputing: 预计算:

$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));

Lookup: 抬头:

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

You could even reduce the scores array to contain only a "valid or not" bool on the right side. 您甚至可以减少分数数组,使其在右侧仅包含“有效或无效”布尔值。 This saves a little bit lookup time and space. 这样可以节省一些查找时间和空间。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM