如何在PHP中创build一个计算器?
我想用PHP来计算简单的代数expression式,例如8*(5+1)
,通过一个普通用户通过一个<input>
标签<input>
(这意味着, 正常的表示法 :没有像Multiply(8, Add(5, 1)))
。 此外,它必须显示所有的步骤,但这并不难。 现在的问题是计算expression式的值。
注:这是我迄今为止所认为的,这是相当低效的,但这是一个provisory解决scheme。 只要可能就replacestring:在我们的例子中,识别string5+1
并将其replace为6
。 然后,再次循环,用(6)
replace(6)
,再次循环,并用48
replace8*6
。 例如,乘法的代码应该如下所示:
for ($a=1; $a < 1000; $a++) { for ($b=1; $b < 1000; $b++) { string_replace($a . '*' . $b, $a*$b, $string); } }
根据您的需要,我会build议寻找调车场的algorithm 。 这很容易实现,工作得很好。
下面是我刚才提到的一个例子: GIST 。
这是代码复制/粘贴到一个块:
expression定义:
class Parenthesis extends TerminalExpression { protected $precidence = 7; public function operate(Stack $stack) { } public function getPrecidence() { return $this->precidence; } public function isNoOp() { return true; } public function isParenthesis() { return true; } public function isOpen() { return $this->value == '('; } } class Number extends TerminalExpression { public function operate(Stack $stack) { return $this->value; } } abstract class Operator extends TerminalExpression { protected $precidence = 0; protected $leftAssoc = true; public function getPrecidence() { return $this->precidence; } public function isLeftAssoc() { return $this->leftAssoc; } public function isOperator() { return true; } } class Addition extends Operator { protected $precidence = 4; public function operate(Stack $stack) { return $stack->pop()->operate($stack) + $stack->pop()->operate($stack); } } class Subtraction extends Operator { protected $precidence = 4; public function operate(Stack $stack) { $left = $stack->pop()->operate($stack); $right = $stack->pop()->operate($stack); return $right - $left; } } class Multiplication extends Operator { protected $precidence = 5; public function operate(Stack $stack) { return $stack->pop()->operate($stack) * $stack->pop()->operate($stack); } } class Division extends Operator { protected $precidence = 5; public function operate(Stack $stack) { $left = $stack->pop()->operate($stack); $right = $stack->pop()->operate($stack); return $right / $left; } } class Power extends Operator { protected $precidence=6; public function operate(Stack $stack) { $left = $stack->pop()->operate($stack); $right = $stack->pop()->operate($stack); return pow($right, $left); } } abstract class TerminalExpression { protected $value = ''; public function __construct($value) { $this->value = $value; } public static function factory($value) { if (is_object($value) && $value instanceof TerminalExpression) { return $value; } elseif (is_numeric($value)) { return new Number($value); } elseif ($value == '+') { return new Addition($value); } elseif ($value == '-') { return new Subtraction($value); } elseif ($value == '*') { return new Multiplication($value); } elseif ($value == '/') { return new Division($value); } elseif ($value == '^') { return new Power($value); } elseif (in_array($value, array('(', ')'))) { return new Parenthesis($value); } throw new Exception('Undefined Value ' . $value); } abstract public function operate(Stack $stack); public function isOperator() { return false; } public function isParenthesis() { return false; } public function isNoOp() { return false; } public function render() { return $this->value; } }
堆栈(真正简单的实现):
class Stack { protected $data = array(); public function push($element) { $this->data[] = $element; } public function poke() { return end($this->data); } public function pop() { return array_pop($this->data); } }
最后,执行者类:
class Math { protected $variables = array(); public function evaluate($string) { $stack = $this->parse($string); return $this->run($stack); } public function parse($string) { $tokens = $this->tokenize($string); $output = new Stack(); $operators = new Stack(); foreach ($tokens as $token) { $token = $this->extractVariables($token); $expression = TerminalExpression::factory($token); if ($expression->isOperator()) { $this->parseOperator($expression, $output, $operators); } elseif ($expression->isParenthesis()) { $this->parseParenthesis($expression, $output, $operators); } else { $output->push($expression); } } while (($op = $operators->pop())) { if ($op->isParenthesis()) { throw new RuntimeException('Mismatched Parenthesis'); } $output->push($op); } return $output; } public function registerVariable($name, $value) { $this->variables[$name] = $value; } public function run(Stack $stack) { while (($operator = $stack->pop()) && $operator->isOperator()) { $value = $operator->operate($stack); if (!is_null($value)) { $stack->push(TerminalExpression::factory($value)); } } return $operator ? $operator->render() : $this->render($stack); } protected function extractVariables($token) { if ($token[0] == '$') { $key = substr($token, 1); return isset($this->variables[$key]) ? $this->variables[$key] : 0; } return $token; } protected function render(Stack $stack) { $output = ''; while (($el = $stack->pop())) { $output .= $el->render(); } if ($output) { return $output; } throw new RuntimeException('Could not render output'); } protected function parseParenthesis(TerminalExpression $expression, Stack $output, Stack $operators) { if ($expression->isOpen()) { $operators->push($expression); } else { $clean = false; while (($end = $operators->pop())) { if ($end->isParenthesis()) { $clean = true; break; } else { $output->push($end); } } if (!$clean) { throw new RuntimeException('Mismatched Parenthesis'); } } } protected function parseOperator(TerminalExpression $expression, Stack $output, Stack $operators) { $end = $operators->poke(); if (!$end) { $operators->push($expression); } elseif ($end->isOperator()) { do { if ($expression->isLeftAssoc() && $expression->getPrecidence() <= $end->getPrecidence()) { $output->push($operators->pop()); } elseif (!$expression->isLeftAssoc() && $expression->getPrecidence() < $end->getPrecidence()) { $output->push($operators->pop()); } else { break; } } while (($end = $operators->poke()) && $end->isOperator()); $operators->push($expression); } else { $operators->push($expression); } } protected function tokenize($string) { $parts = preg_split('((\d+|\+|-|\(|\)|\*|/)|\s+)', $string, null, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); $parts = array_map('trim', $parts); return $parts; } }
它首先标记input(基于字边界和令牌)。 然后,它运行Shunting Yardalgorithm将input转换为RPN(逆波兰标记)堆栈。 然后,这只是执行堆栈的问题。 这里有一个简单的例子:
$math = new Math(); $answer = $math->evaluate('(2 + 3) * 4'); var_dump($answer); // int(20) $answer = $math->evaluate('1 + 2 * ((3 + 4) * 5 + 6)'); var_dump($answer); // int(83) $answer = $math->evaluate('(1 + 2) * (3 + 4) * (5 + 6)'); var_dump($answer); // int(231) $math->registerVariable('a', 4); $answer = $math->evaluate('($a + 3) * 4'); var_dump($answer); // int(28) $math->registerVariable('a', 5); $answer = $math->evaluate('($a + $a) * 4'); var_dump($answer); // int(40)
现在,这个例子比你可能需要的复杂得多。 原因是它也处理分组和运算符优先级。 但是,这是运行algorithm的一个体面的例子,它不使用EVAL并支持variables。
有一个mathparsing器类称为bcParserPHP可能是有趣的。
似乎相当简单的使用和相当强大。
来自其网站的示例代码:
$parser = new MathParser(); $parser->setVariable('X', 5); $parser->setVariable('Y', 2); $parser->setExpression('COS(X)+SIN(Y)/2'); echo $parser->getValue();
不幸的是,这是一个商业产品。 我不知道这是否会阻止你使用它(猜测取决于价格和你的需求)。
一个非商业的select可能是这样的: http : //www.phpclasses.org/package/2695-PHP-Safely-evaluate-mathematical-expressions.html
请注意,这个类在内部使用eval()
,如果可能,我会避免这样做。
否则,编写自己的语言parsing器将是理想的解决scheme,但在PHP中这样做并不明智。
我将首先剥离任何不应该在expression式中的input(假设你只是想允许加,减,乘,除以及没有variables):
$expr = preg_replace('/[^0-9+*\/-]/', '', $expr);
然后,一旦我确信用户input中没有任何危险,只需通过eval()来评估expression式:
$result = eval("return $expr;");
没有必要重新发明轮子。
编辑纳入Kolink的更正。 谢谢!