diff options
author | NeonXP <neonxp@NeonXP-VAIO.(none)> | 2013-03-14 04:27:37 +0400 |
---|---|---|
committer | NeonXP <neonxp@NeonXP-VAIO.(none)> | 2013-03-14 04:27:37 +0400 |
commit | a8bdd54346f8b932ede78f7a9a0764ee336fd281 (patch) | |
tree | 86e9ee34dca9c9758e50b3b96d1a9500799c498b |
Initial commit
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | NXP/MathExecutor.php | 197 | ||||
-rw-r--r-- | NXP/Tests/MathTest.php | 60 | ||||
-rw-r--r-- | README.md | 10 | ||||
-rw-r--r-- | composer.json | 14 | ||||
-rw-r--r-- | test.php | 11 |
6 files changed, 294 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f33dd5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +vendor/ +.idea/
\ No newline at end of file diff --git a/NXP/MathExecutor.php b/NXP/MathExecutor.php new file mode 100644 index 0000000..d7eaf82 --- /dev/null +++ b/NXP/MathExecutor.php @@ -0,0 +1,197 @@ +<?php +/** + * Author: Alexander "NeonXP" Kiryukhin + * Date: 14.03.13 + * Time: 1:01 + */ +namespace NXP; + +/** + * Class MathExecutor + * @package NXP + */ +class MathExecutor { + const LEFT_ASSOCIATED = 'LEFT_ASSOCIATED'; + const RIGHT_ASSOCIATED = 'RIGHT_ASSOCIATED'; + const NOT_ASSOCIATED = 'NOT_ASSOCIATED'; + + const UNARY = 'UNARY'; + const BINARY = 'BINARY'; + + private $operators = [ ]; + + /** + * Base math operators + */ + public function __construct() + { + $this->addOperator('+', 1, function ($op1, $op2) { return $op1 + $op2; }); + $this->addOperator('-', 1, function ($op1, $op2) { return $op1 - $op2; }); + $this->addOperator('*', 2, function ($op1, $op2) { return $op1 * $op2; }); + $this->addOperator('/', 2, function ($op1, $op2) { return $op1 / $op2; }); + $this->addOperator('^', 3, function ($op1, $op2) { return pow($op1, $op2); }); + } + + /** + * Add custom operator + * @param string $name + * @param int $priority + * @param callable $callback + * @param string $association + * @param string $type + */ + public function addOperator($name, $priority, callable $callback, $association = self::LEFT_ASSOCIATED, $type = self::BINARY) + { + $this->operators[$name] = [ + 'priority' => $priority, + 'association' => $association, + 'type' => $type, + 'callback' => $callback + ]; + } + + /** + * Execute expression + * @param $expression + * @return int|float + */ + public function execute($expression) + { + $reversePolishNotation = $this->convertToReversePolishNotation($expression); + $result = $this->calculateReversePolishNotation($reversePolishNotation); + + return $result; + } + + /** + * Convert expression from normal expression form to RPN + * @param $expression + * @return array + * @throws \Exception + */ + protected function convertToReversePolishNotation($expression) + { + $stack = new \SplStack(); + $queue = []; + $currentNumber = ''; + + for ($i = 0; $i < strlen($expression); $i++) + { + $char = substr($expression, $i, 1); + if (is_numeric($char) || (($char == '.') && (strpos($currentNumber, '.')===false))) { + $currentNumber .= $char; + } elseif ($currentNumber!='') { + $queue = $this->insertNumberToQueue($currentNumber, $queue); + $currentNumber = ''; + } + if (array_key_exists($char, $this->operators)) { + while ($this->o1HasLowerPriority($char, $stack)) { + $queue[] = $stack->pop(); + } + $stack->push($char); + } + if ($char == '(') { + $stack->push($char); + } + if ($char == ')') { + if ($currentNumber!='') { + $queue = $this->insertNumberToQueue($currentNumber, $queue); + $currentNumber = ''; + } + while (($stackChar = $stack->pop()) != '(') { + $queue[] = $stackChar; + } + /** + * @TODO parse functions here + */ + } + } + + if ($currentNumber!='') { + $queue = $this->insertNumberToQueue($currentNumber, $queue); + } + + while (!$stack->isEmpty()) { + $queue[] = ($char = $stack->pop()); + if (!array_key_exists($char, $this->operators)) { + throw new \Exception('Opening bracket has no closing bracket'); + } + } + + return $queue; + } + + /** + * Calculate value of expression + * @param array $expression + * @return int|float + * @throws \Exception + */ + protected function calculateReversePolishNotation(array $expression) + { + $stack = new \SplStack(); + foreach ($expression as $element) { + if (is_numeric($element)) { + $stack->push($element); + } elseif (array_key_exists($element, $this->operators)) { + $operator = $this->operators[$element]; + switch ($operator['type']) { + case self::BINARY: + $op2 = $stack->pop(); + $op1 = $stack->pop(); + $operatorResult = $operator['callback']($op1, $op2); + break; + case self::UNARY: + $op = $stack->pop(); + $operatorResult = $operator['callback']($op); + break; + default: + throw new \Exception('Incorrect type'); + } + $stack->push($operatorResult); + } + } + $result = $stack->pop(); + if (!$stack->isEmpty()) { + throw new \Exception('Incorrect expression'); + } + + return $result; + } + + /** + * @param $char + * @param $stack + * @return bool + */ + private function o1HasLowerPriority($char, \SplStack $stack) { + if (($stack->isEmpty()) || ($stack->top() == '(')) { + return false; + } + $stackTopAssociation = $this->operators[$stack->top()]['association']; + $stackTopPriority = $this->operators[$stack->top()]['priority']; + $charPriority = $this->operators[$char]['priority']; + + + return + (($stackTopAssociation != self::LEFT_ASSOCIATED) && ($stackTopPriority > $charPriority)) || + (($stackTopAssociation == self::LEFT_ASSOCIATED) && ($stackTopPriority >= $charPriority)); + } + + /** + * @param string $currentNumber + * @param array $queue + * @return array + */ + private function insertNumberToQueue($currentNumber, $queue) + { + if ($currentNumber[0]=='.') { + $currentNumber = '0'.$currentNumber; + } + $currentNumber = trim($currentNumber, '.'); + $queue[] = $currentNumber; + + return $queue; + } + +}
\ No newline at end of file diff --git a/NXP/Tests/MathTest.php b/NXP/Tests/MathTest.php new file mode 100644 index 0000000..6ab67ee --- /dev/null +++ b/NXP/Tests/MathTest.php @@ -0,0 +1,60 @@ +<?php +/** + * Author: Alexander "NeonXP" Kiryukhin + * Date: 14.03.13 + * Time: 3:41 + */ +namespace NXP\Tests; + +use \NXP\MathExecutor; + +class MathTest extends \PHPUnit_Framework_TestCase { + public function setup() + { + require '../MathExecutor.php'; + } + public function testCalculating() + { + $calculator = new MathExecutor(); + for ($i = 1; $i <= 10; $i++) { + $expression = $this->generateExpression(); + print "Test #$i. Expression: '$expression'\t"; + + eval('$result1 = ' . $expression . ';'); + print "PHP result: $result1 \t"; + $result2 = $calculator->execute($expression); + print "NXP Math Executor result: $result2\n"; + $this->assertEquals($result1, $result2); + } + } + + private function generateExpression() + { + $operators = [ '+', '-', '*', '/' ]; + $number = true; + $expression = ''; + $brackets = 0; + for ($i = 1; $i < rand(1,10)*2; $i++) { + if ($number) { + $expression .= rand(1,100)*0.5; + } else { + $expression .= $operators[rand(0,3)]; + } + $number = !$number; + $rand = rand(1,5); + if (($rand == 1) && ($number)) { + $expression .= '('; + $brackets++; + } elseif (($rand == 2) && (!$number) && ($brackets > 0)) { + $expression .= ')'; + $brackets--; + } + } + if ($number) { + $expression .= rand(1,100)*0.5; + } + $expression .= str_repeat(')', $brackets); + + return $expression; + } +}
\ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..474a47d --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# NXP MathExecutor + +Simple math expressions calculator + +## Sample usage: + + <?php + require "vendor/autoload.php"; + $calculator = new \NXP\MathExecutor(); + print $calculator->execute("1 + 2 * (2 - (4+10))^2");
\ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..1eb0427 --- /dev/null +++ b/composer.json @@ -0,0 +1,14 @@ +{ + "name": "NXP/MathExecutor", + "description": "Simple math expressions calculator", + "minimum-stability": "stable", + "authors": [ + { + "name": "Alexander 'NeonXP' Kiryukhin", + "email": "frei@neonxp.info" + } + ], + "autoload": { + "psr-0": {"NXP": "."} + } +} diff --git a/test.php b/test.php new file mode 100644 index 0000000..37089be --- /dev/null +++ b/test.php @@ -0,0 +1,11 @@ +<?php +/** + * Author: Alexander "NeonXP" Kiryukhin + * Date: 14.03.13 + * Time: 1:08 + */ +require "vendor/autoload.php"; + +$e = new \NXP\MathExecutor(); + +print $e->execute("1 + 2 * (2 - (4+10))^2");
\ No newline at end of file |