Низкая производительность игры в PHP Dart
Я сделал класс, чтобы рассчитать выбросы на основе оценки.
Например, если оценка равна 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
Ответы
Ответ 1
Я использовал генерацию основных подстановок и очень быстро (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 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 одиночный, двойной или тройной, бычий и двойной бык. Если есть больше или специальные броски, вы можете их добавить.
Серийная сортировка + сортирует, чтобы быстро удалить дубликаты. Для целей проверки броска может оказаться полезным оставить дубликаты.
Вы могли бы принять обозначение как строку, например: S10D20BULL
или DBULLDBULLT20
. Если вы представите S
для синглов, у вас никогда не будет путаницы. D112T20
является двусмысленным, это D11S2T20
или D1S12T20
? Строки легче работать, поэтому вы можете даже повысить производительность. Разделение строковой нотации на части немного немного сложно, но выполнимо.
Обратите внимание, что я не добавлял специальные проверки для >170
или 1
, потому что список будет просто пустым. Это оптимизация, которую вы могли бы применить.
Вы можете дополнительно добавить бросок miss
со счетом 0
.
Я не совсем понимаю ваш код, он слишком сложный. Я думаю, вы теряете много времени, делая сортировку и преобразование между обозначениями. Как вы можете видеть в моем решении, я быстро создаю результирующий набор и делаю расчеты и формирование записи в одно и то же время.
Я должен также упомянуть, что я не слишком хорошо знаком с правилами дарт. Я предполагал быка и двойного быка, но если один бык и бык точны, не стесняйтесь исправлять меня.
Ответ 2
- Первое, что я обнаружил, это то, что ваш класс не возвращает все возможные броски для данного балла.
- Во-вторых, это очень медленно.
Но это меня заинтересовало, поэтому я сделал свою собственную версию класса, чтобы решить эту проблему. Поскольку существующий, который вы дали здесь, выглядит немного сложным для проблемы. Просто хотел сделать это простым, и простой также должен быть быстрым.
Я сделал некоторые предположения, чтобы начать с:
- Добавлен "промах" с 0 баллами (вы можете просто прокомментировать это, если не нужно).
- Установите максимальный результат до 170, как и вы.
- Предполагаемый класс должен возвращать все возможные выбросы.
Я устанавливаю свойство с массивом, который содержит все возможные побеги и их счет. И сделал способ вернуть все возможные броски для данного балла.
Я установил тест для своего класса, чтобы дать вам некоторые номера производительности. Я запустил все значения баллов от 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;
}
}
Ответ 3
Если вы хотите получить дополнительный прирост производительности, вам нужно сохранить результаты для всех возможностей в базе данных или файле и просто читать значения, а не вычислять их каждый раз.
Я бы предложил следующую структуру
Table (checkout)
sum (int) | dart1 (int) | dart2 (int) | dart3 (int)
60 | 20 | 20 | 31
все дротики, указывающие на следующую таблицу
Table (dart)
pk (int) | bez (varchar)
20 | 20
31 | D10
Ответ 4
Не уверен, какова природа вашей ошибки, потому что я не знаком с правилами дартс, но строка 129 определенно неверна:
if ($x <= 20 || $x == 50 || $x == 25 || ($x % 3 == 0) || ($x <= 40 && ($x % 2 == 0))) {
Либо вы хотите тестировать $xx, либо не хотите повторно тестировать условие, которое уже привело вас к этой строке. В настоящий момент внутренняя петля всегда будет вызываться до тех пор, пока цикл $xx будет. Это убийца производительности. Конечно, наличие вложенного цикла внутри вложенного цикла, как правило, независимо от него.
Другой подход может заключаться в создании справочной таблицы с ответами на все возможные оценки, как это было предложено Арканоном.
Ответ 5
Вот моя попытка.
Результат сортируется лексикографически по возрастанию индивидуальных оценок.
- Если вы сделаете цикл foreach, вы получите результаты перемещения 1 для перемещения n.
- Если вы получаете доступ по индексу, вы получите ходы в обратном порядке (1 = последний ход, 3 = первый ход).
Это очень быстро для 3 ходов, но рушится, если вы попробуете больше, чем это.
Возможно (теоретически) вычислить возможности для любого количества ходов, хотя скоро вы достигнете максимального времени выполнения script:).
Максимально достижимый балл равен 180 (3x тройной 20). Вы можете тратить много процессорных средств на более высокий балл. Это приведет к пустым результатам.
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>";