PHP Dart游戏计算性能缓慢

时间:2014-01-19 19:12:43

标签: php class permutation

我根据分数计算了一次计算。

例如,如果分数当前为140,则该类返回一个包含可能抛出的集合的数组:

[10] => Array
    (
        [0] => T18
        [1] => T18
        [2] => D16
    )

[11] => Array
    (
        [0] => T18
        [1] => T16
        [2] => D19
    )

[13] => Array
    (
        [0] => T17
        [1] => T17
        [2] => D19
    )

[14] => Array
    (
        [0] => 50
        [1] => 50
        [2] => D20

但是计算这些东西的速度很慢。有什么方法可以优化这个课程吗?

<?php
/**
 * PHP Dartgame calculating class
 * @author Youri van den Bogert
 */

class Darts {

    /**
     * @var string
     */
    public static $notation_triple = 'T';

    /**
     * @var string
     */
    public static $notation_double = 'D';

    /**
     * @var int
     */
    private static $maxCheckout = 170;

    /**
     * @var string
     */
    private static $doubleBull = 'Bull';

    /**
     * @var string
     */
    private static $singleBull = 'Single';

    /**
     * @var array
     */
    private static $scoreSheet = array('1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '25', '50');

    /**
     * Get a total thrown score
     * @param $score1
     * @param $score2
     * @param $score3
     * @return array
     */
    public static function getTotalScore ($score1, $score2, $score3) {
        return array(
          'dart1' => self::getScoreOfDart($score1),
          'dart2' => self::getScoreOfDart($score2),
          'dart3' => self::getScoreOfDart($score3),
          'total' => self::getScoreOfDart($score1) + self::getScoreOfDart($score2) + self::getScoreOfDart($score3)
        );
    }


    /**
     * Get score of a single dart
     * @param $score
     * @return mixed
     */
    public static function getScoreOfDart ($score) {

        if (is_numeric($score)) {
            return $score;
        }

        if ($score[0] == self::$notation_triple) {
            $multiplier = 3;
        } elseif ($score[0] == self::$notation_double) {
            $multiplier = 2;
        } else {
            $multiplier = 1;
        }

        $correctScore = filter_var($score, FILTER_SANITIZE_NUMBER_INT);

        return ($correctScore * $multiplier);

    }

    public static function getScoreSheet () {

        return self::$scoreSheet;

    }

    public static function calculatePossibleCheckout ($currentScore) {

        // We cant checkout higher then $maxCheckout
        if ($currentScore > self::$maxCheckout || $currentScore == 1) {
            return false;
        }

        // Return bull
        if ($currentScore == 50) {
            return array(
                'dart1' => self::$doubleBull
            );
        }

        if ($currentScore == self::$maxCheckout) {
            return array(
                'dart1' => self::$notation_triple . '20',
                'dart2' => self::$notation_triple . 'T20',
                'dart3' => 'Bull'
            );
        }

        $lastScore = $currentScore;
        $lastPossibleThrow = 0;
        $checkOut = array();

        // Can current score be checked out?
        if (self::canScore($currentScore) == true) {

            return array(
                'dart1' => self::$notation_double . ($currentScore / 2)
            );

        // Current score can't be checked out - calculate what to throw
        } else {

            for ($x=60; $x >= 0; --$x) {

                if ($x <= 20 || $x == 50 || $x == 25 || ($x % 3 == 0) || ($x <= 40 && ($x % 2 == 0))) {

                    for ($xx=60; $xx >= 0; --$xx) {

                        if ($x <= 20 || $x == 50 || $x == 25 || ($x % 3 == 0) || ($x <= 40 && ($x % 2 == 0))) {

                            for ($xxx=50; $xxx > 0; $xxx = $xxx - 2) {

                                if ($xxx == 48) {
                                    $xxx = 40;
                                }

                                if (self::checkIfScoreExists($xxx) == true && self::checkIfScoreExists($xx) == true && self::checkIfScoreExists($x) == true && ($xxx + $xx + $x) == $currentScore) {

                                    $score_1 = self::getCorrectDartName($xxx);
                                    $score_2 = self::getCorrectDartName($xx);
                                    $score_3 = self::getCorrectDartName($x, true);

                                    if ($score_1[0] == 'D' || $score_2[0] == 'D' || $score_3[0] == 'D') {
                                        $nextKey = (count($checkOut)+1);
                                        if ($xxx != 0) $checkOut[$nextKey][] = $score_1;
                                        if ($xx != 0) $checkOut[$nextKey][] = $score_2;
                                        if ($x != 0) $checkOut[$nextKey][] = $score_3;

                                        usort($checkOut[$nextKey], function($a, $b) {
                                            if (is_int($a) || is_float($a)) {
                                                if (is_int($b) || is_float($b)) {
                                                    return $a - $b;
                                                }
                                                else
                                                    return -1;
                                            }
                                            elseif (is_int($b) || is_float($b)) {
                                                return 1;
                                            }
                                            else {
                                                return strcmp($b, $a);
                                            }
                                        });
                                    }

                                }
                            }
                        }
                    }
                }
            }

        }

        return array_unique($checkOut, SORT_REGULAR);

    }

    public static function getCorrectDartName ($total, $isLast = false) {

        if ($total == 25 || $total == 50) {
            return $total;
        }

        if ($total < 20 && $isLast == false) {
            return $total;
        }

        if ($total %3 == 0) {
            return self::$notation_triple . ($total/3);
        } elseif ($total %2 == 0) {
            return self::$notation_double . ($total/2);
        }

        return $total;


    }

    /**
     * Check if score exists
     * @param $score
     * @return bool
     */
    public static function checkIfScoreExists ($score) {

        if ($score == 50 || $score == 25 || $score == 0) return true;

        $possibleScores = array_merge(range(1,20));

        foreach ($possibleScores as $posScore) {
            if ($score == self::getScoreOfDart(self::$notation_double . $posScore) || $score == self::getScoreOfDart(self::$notation_triple . $posScore) || $score == $posScore) {
                return true;
            }
        }

        return false;

    }

    /**
     * Check if a specific score can be thrown by one dart
     * @param $score
     * @return bool
     */
    public static function canScore ($score) {
        if ($score == 50) {
            return true;
        } elseif ($score < 40 || $score == 40) {

            if ($score % 2 == 0) {
                return true; // Score is even - so its possible to throw
            } else {
                return false;
            }
        }

        return false;
    }


} 

指向课程的链接:https://gist.github.com/YOUR1/8509498

5 个答案:

答案 0 :(得分:7)

我使用了基本排列生成,它超快(0.06秒)。它当然可以优化,但我认为没有意义,因为它已经如此之快。

<?php

class DartUtils {

    private static $possible_points;

    public static function getPossibleThrowsForScore($score) {

        // generate all possible single throws and their score
        // I didn't want to write them all out
        // but it's certainly an option (and faster)
        self::$possible_points = array();
        for ($i = 1; $i <= 20; $i += 1) {
            self::$possible_points["S" . $i] = $i; // S = single
            self::$possible_points["D" . $i] = $i * 2;
            self::$possible_points["T" . $i] = $i * 3;
        }
        self::$possible_points["bull"] = 25;
        self::$possible_points["double bull"] = 50;
        // self::$possible_points["miss"] = 0;

        $throws = self::findSatisfyingThrowsForScore($score, 3, array());
        foreach ($throws as $i => $serialized_throw) {
            $throws[$i] = unserialize($serialized_throw);
        }
        return $throws;
    }

    private static function findSatisfyingThrowsForScore($score, $num_throws, $base_notation) {
        $possible_throws = array();
        foreach (self::$possible_points as $notation => $value) {
            if ($num_throws === 1) { // we've done all throws
                if ($score - $value === 0) { // we satisfied the score
                    $throw = array_merge($base_notation, array($notation));
                    sort($throw);
                    $possible_throws[] = serialize($throw);
                }
            } else {
                // so long as there are num_throws, recurse with all possible throws
                $possible_throws = array_merge($possible_throws,
                  self::findSatisfyingThrowsForScore($score - $value, $num_throws - 1, array_merge($base_notation, array($notation))));
            }
        }
        $possible_throws = array_unique($possible_throws);
        sort($possible_throws);
        return $possible_throws;
    }

}

var_dump(DartUtils::getPossibleThrowsForScore(140));

输出结果为:

array(21) {
  [0]=>
  array(3) {
    [0]=>
    string(3) "D10"
    [1]=>
    string(3) "T20"
    [2]=>
    string(3) "T20"
  }
  [1]=>
  array(3) {
    [0]=>
    string(3) "D13"
    [1]=>
    string(3) "T18"
    [2]=>
    string(3) "T20"
  }
  [2]=>
  array(3) {
    [0]=>
    string(3) "D13"
    [1]=>
    string(3) "T19"
    [2]=>
    string(3) "T19"
  }
  [3]=>
  array(3) {
    [0]=>
    string(3) "D15"
    [1]=>
    string(3) "T20"
    [2]=>
    string(11) "double bull"
  }
  [4]=>
  array(3) {
    [0]=>
    string(3) "D16"
    [1]=>
    string(3) "T16"
    [2]=>
    string(3) "T20"
  }
  [5]=>
  array(3) {
    [0]=>
    string(3) "D16"
    [1]=>
    string(3) "T17"
    [2]=>
    string(3) "T19"
  }
  [6]=>
  array(3) {
    [0]=>
    string(3) "D16"
    [1]=>
    string(3) "T18"
    [2]=>
    string(3) "T18"
  }
  [7]=>
  array(3) {
    [0]=>
    string(3) "D18"
    [1]=>
    string(3) "T18"
    [2]=>
    string(11) "double bull"
  }
  [8]=>
  array(3) {
    [0]=>
    string(3) "D19"
    [1]=>
    string(3) "T14"
    [2]=>
    string(3) "T20"
  }
  [9]=>
  array(3) {
    [0]=>
    string(3) "D19"
    [1]=>
    string(3) "T15"
    [2]=>
    string(3) "T19"
  }
  [10]=>
  array(3) {
    [0]=>
    string(3) "D19"
    [1]=>
    string(3) "T16"
    [2]=>
    string(3) "T18"
  }
  [11]=>
  array(3) {
    [0]=>
    string(3) "D19"
    [1]=>
    string(3) "T17"
    [2]=>
    string(3) "T17"
  }
  [12]=>
  array(3) {
    [0]=>
    string(3) "D20"
    [1]=>
    string(11) "double bull"
    [2]=>
    string(11) "double bull"
  }
  [13]=>
  array(3) {
    [0]=>
    string(3) "D20"
    [1]=>
    string(3) "D20"
    [2]=>
    string(3) "T20"
  }
  [14]=>
  array(3) {
    [0]=>
    string(3) "S20"
    [1]=>
    string(3) "T20"
    [2]=>
    string(3) "T20"
  }
  [15]=>
  array(3) {
    [0]=>
    string(3) "T10"
    [1]=>
    string(3) "T20"
    [2]=>
    string(11) "double bull"
  }
  [16]=>
  array(3) {
    [0]=>
    string(3) "T11"
    [1]=>
    string(3) "T19"
    [2]=>
    string(11) "double bull"
  }
  [17]=>
  array(3) {
    [0]=>
    string(3) "T12"
    [1]=>
    string(3) "T18"
    [2]=>
    string(11) "double bull"
  }
  [18]=>
  array(3) {
    [0]=>
    string(3) "T13"
    [1]=>
    string(3) "T17"
    [2]=>
    string(11) "double bull"
  }
  [19]=>
  array(3) {
    [0]=>
    string(3) "T14"
    [1]=>
    string(3) "T16"
    [2]=>
    string(11) "double bull"
  }
  [20]=>
  array(3) {
    [0]=>
    string(3) "T15"
    [1]=>
    string(3) "T15"
    [2]=>
    string(11) "double bull"
  }
}

我添加的投注是:1-20单,双或三,公牛和双公牛。如果有更多或特殊投掷,您可以添加它们。

sort + serialization是快速删除重复项的技巧。对于抛出验证目的,将重复项留在其中可能实际上是有益的。

您可以考虑将表示法接受为字符串,例如:S10D20BULLDBULLDBULLT20。如果你为单身人士介绍S,你永远不会有困惑。 D112T20是不明确的,是D11S2T20还是D1S12T20?字符串更容易使用,因此您甚至可以获得性能。将字符串符号拆分回它的部分有点棘手但可行。

请注意,我没有为>1701添加特殊检查,因为列表只是空的。这是您可以应用的优化。

您可以选择添加 miss 投掷,评分为0


我不太了解你的代码,它太复杂了。我认为你正在失去很多时间进行排序和表示法之间的转换。正如您在我的解决方案中所看到的,我快速构建结果集并同时进行分数计算和符号生成。


我还应该提到我对飞镖规则不太熟悉。我假设 bull 双牛但是如果单牛 bull 是准确的,请随时纠正我。

答案 1 :(得分:2)

  • 我发现的第一件事就是你的班级没有给出所有可能的分数。
  • 其次是真的很慢。

但这让我感兴趣所以我制作了自己的课程版本来解决这个问题。正如你现在给出的现有问题看起来有点复杂。只是想让它变得简单,而且简单也应该很快。

我从一开始做了一些假设:

  1. 添加了0分的'miss'拍摄(如果不需要,您可以将其评论出来)。
  2. 将最高分数设置为170,就像您一样。
  3. 假设班级应该返回所有可能的投掷。
  4. 我用数组设置属性,该数组包含所有可能的射击和他们的分数。并制定方法返回给定分数的所有可能的抛出。

    我为我的课程设置了一个测试,为你提供一些性能数字。我已经在循环中逐个运行所有得分值1到170,以获得每个可能的结帐结果。

    以下是我机器上的结果:

    • 总分170(1 - 170)。
    • 250026总分数结果。
    • 总运行时间为4秒。

    平均每秒42.5分,平均每秒62506.5分结果。

    因此它返回所有可能的结果,并且速度更快。希望这会有所帮助。如果没有别的可能会让你知道如何提高你的课程。

    <?php
    
    $darts = new Darts();
    
    $result = $darts->possibleCheckout(60); //gives 3767 results
    
    //var_dump($result);
    echo count($result) . "\n";
    
    
    
    /**
     * PHP Dartgame calculating class
     * @author Aleksandar Popovic
     */
    class Darts
    {
        /**
         * All possible shoots and score per each
         * @var array
         */
        private $shoot_score = array(
            'miss' => 0,
            '1' => 1,
            '2' => 2,
            '3' => 3,
            '4' => 4,
            '5' => 5,
            '6' => 6,
            '7' => 7,
            '8' => 8,
            '9' => 9,
            '10' => 10,
            '11' => 11,
            '12' => 12,
            '13' => 13,
            '14' => 14,
            '15' => 15,
            '16' => 16,
            '17' => 17,
            '18' => 18,
            '19' => 19,
            '20' => 20,
            'D1' => 2,
            'D2' => 4,
            'D3' => 6,
            'D4' => 8,
            'D5' => 10,
            'D6' => 12,
            'D7' => 14,
            'D8' => 16,
            'D9' => 18,
            'D10' => 20,
            'D11' => 22,
            'D12' => 24,
            'D13' => 26,
            'D14' => 28,
            'D15' => 30,
            'D16' => 32,
            'D17' => 34,
            'D18' => 36,
            'D19' => 38,
            'D20' => 40,
            'T1' => 3,
            'T2' => 6,
            'T3' => 9,
            'T4' => 12,
            'T5' => 15,
            'T6' => 18,
            'T7' => 21,
            'T8' => 24,
            'T9' => 27,
            'T10' => 30,
            'T11' => 33,
            'T12' => 36,
            'T13' => 39,
            'T14' => 42,
            'T15' => 45,
            'T16' => 48,
            'T17' => 51,
            'T18' => 54,
            'T19' => 57,
            'T20' => 60,
            'Signle-Bull' => 25,
            'Double-Bull' => 50
        );
    
         /**
         * Maximum score
         * @var int
         */
        private $max_score = 170; // 3 x T20 is max right?
    
    
        /**
         * Return all possible checkouts for given score
         * @param int $current_score
         * @return array
         */
        public function possibleCheckout($current_score)
        {
            if ($current_score > $this->max_score || $current_score < 1)
            {
                return false;
            }
    
            $checkout = array();
            foreach ($this->shoot_score as $shoot1 => $score1)
            {
                if ($score1 > $current_score)
                {
                    continue;
                }
    
                foreach ($this->shoot_score as $shoot2 => $score2)
                {
                    if ($score1 + $score2 > $current_score)
                    {
                        continue;
                    }
    
                    foreach ($this->shoot_score as $shoot3 => $score3)
                    {
                        if ($score1 + $score2 + $score3 == $current_score)
                        {
                            $checkout[] = array($shoot1, $shoot2, $shoot3);
                            continue;
                        }
                    }
                }
            }
    
            return $checkout;
        }
    }
    

答案 2 :(得分:2)

如果您希望获得额外的性能提升,则必须保存数据库或文件中所有可能性的结果,只需读取值而不是每次都计算它们。

我会建议以下结构

Table (checkout)

sum (int) | dart1 (int) | dart2 (int) | dart3 (int)

60 | 20 | 20 | 31

指向下表的所有飞镖

Table (dart)

pk (int) | bez (varchar)

20 | 20

31 | D10

答案 3 :(得分:1)

不确定你的错误的性质是什么,因为我不熟悉飞镖的规则,但第129行肯定是错的:

if ($x <= 20 || $x == 50 || $x == 25 || ($x % 3 == 0) || ($x <= 40 && ($x % 2 == 0))) {

要么您想要测试$ xx,要么您不想重新测试已经引导您进入该行的条件。目前,只要$ xx循环,就会调用最内层的循环。这是一个性能杀手。当然,在嵌套循环中嵌套循环通常是无论如何。

另一种方法可能是生成一个查找表,其中包含所有可能得分的答案,如Arkanon所建议。

答案 4 :(得分:1)

这是我的尝试。

输出按字典顺序按升序个别分数排序。

  • 如果你做了一个foreach循环,你将得到移动1的结果来移动n。
  • 如果按索引访问,则会以相反的顺序获取移动(1 =最后一步,3 =第一步)。

3次移动速度非常快,但如果尝试的话多,那就会崩溃。

有可能(理论上)计算任意数量的移动的可能性,尽管你很快就会达到脚本最大执行时间:)。

最大可达成分为180分(3倍三倍20分)。你可以浪费大量的CPU尝试更高的分数。这将是一个空洞的结果。

class darts {
    const DATAFILE = "darts-scores.txt"; // score names & values database
    static $score_val;  // values of scores
    static $score_name; // names of scores
    static $score_num;  // # of score values

    static $res;        // search result
    static $tries;      // for statistics

    // internal search
    static private function moves ($score, $moves, $i=0, &$list = array())
    {
        self::$tries++; // update stats

        // search from the current scores only
        for ( ; $i != self::$score_num; $i++)
        {
            $val = self::$score_val[$i];
            if ($val > $score) break;  // no need to try these ones
            if ($moves == 1) // unrolled the recursion to improve performances
            {
                if ($score == $val)
                {
                    // found another combination
                    $list[$moves] = self::$score_name[$i];
                    self::$res[] = $list;
                }
            }
            else // go down to seek the rest of the combination
            {
                $list[$moves] = self::$score_name[$i];
                self::moves ($score - $val, $moves-1, $i, $list);
            }
        }
    }

    // public search function
    static function moves_for_score ($score, $moves=3)
    {
        self::$res = array();
        self::$tries=0;
        self::moves ($score, $moves);
        return self::$res;
    }

    // turn results into a string
    static function flatten ($res)
    {
        return implode (", ",
            array_map (
                function ($e){ return "[".implode(':',$e)."]"; },
                $res));
    }

    // initialize scores table
    static function init ()
    {
        if (!file_exists (self::DATAFILE))
        {
            // you can change the names of the scores with these two lines
            $scores = array (
                "miss" =>0, 
                "bull"=>25, 
                "double bull"=>50);
            $type = array (
                1=>"S", 
                2=>"D", 
                3=>"T");

            // generate all scores
            for ($t = 1 ; $t <= 3 ; $t++)
            for ($i = 1 ; $i <= 20 ; $i++)
            {
                $scores[$type[$t].$i] = $t * $i;
            }
            asort ($scores);
            foreach ($scores as $name=>$val) $out[] = "$name:$val";
            file_put_contents (self::DATAFILE, implode ("\n", $out));
        }

        // read score file
        $in = preg_split ("/[:\n]/", file_get_contents (self::DATAFILE));
        self::$score_num = count($in)/2;
        for ($i = 0 ; $i != self::$score_num ; $i++)
        {
            self::$score_name[$i] = $in[$i*2];
            self::$score_val [$i] = (int) $in[$i*2+1];
        }
    }
}
darts::init();

////////////////////////////////////////////////////
// basic usage
////////////////////////////////////////////////////
$score = 281;
$moves = 5;
$res = darts::moves_for_score ($score, $moves);

echo "Sequences for $score in $moves moves: "
     .darts::flatten($res)
     ."<br>";
echo "<pre>".print_r($res, true)."</pre><br>";

////////////////////////////////////////////////////
// stress test
////////////////////////////////////////////////////

echo "<br>Computing all possible sequences from 0 to 181...";
$start = microtime(true);
$tries = 0;
$max = 0;
for ($i = 0 ; $i <=180 ; $i++)
{
    $res = darts::moves_for_score ($i);
    $flat = darts::flatten($res);
    if (strlen ($flat) > $max) 
    { 
        $max = strlen($flat); 
        $res_max = $res; 
        $i_max = $i;
    }
    $tries += darts::$tries;
}
echo "done in ".(microtime(true)-$start)."s, $tries tries<br>";
echo "<br>Longest list of possibilities:<br>$i_max -> "
    .darts::flatten ($res_max)."<br>";