简体   繁体   English

如何用PHP制作计算器?

[英]How to make a calculator in PHP?

I want to use PHP to calculate simple algebraic expressions like, 8*(5+1) , entered via an <input> tag by a normal user (which means, normal notation : no syntax changes like Multiply(8, Add(5, 1))) . 我想使用PHP计算普通代数用户通过<input>标记<input>简单代数表达式,例如8*(5+1) (这是正常表示法 :没有语法变化,例如Multiply(8, Add(5, 1))) Also, it has to show all steps, but that's not hard. 另外,它必须显示所有步骤,但这并不困难。 The problem, right now, is calculating the value of the expressions. 现在的问题是计算表达式的值。

Note: this is what I thought so far, which is quite inefficient but it's a provisory solution. 注意:这是我到目前为止的想法,虽然效率很低,但这是临时的解决方案。 Just replace strings where possible: in our example, recognize the string 5+1 and replace it with 6 . 只要可能就替换字符串:在我们的示例中,识别字符串5+1并将其替换为6 Then, loop again, replace (6) with 6 , loop again, and replace 8*6 with 48 . 然后,再次循环,将(6)替换为6 ,再次循环,然后将8*6替换为48 The code for multiplying, for example, should look like this: 例如,用于乘法的代码应如下所示:

for ($a=1; $a < 1000; $a++) {
    for ($b=1; $b < 1000; $b++) {
        string_replace($a . '*' . $b, $a*$b, $string);
    }
}

Depending on your needs, I would suggest looking into the Shunting Yard Algorithm . 根据您的需求,建议您使用Shunting Yard Algorithm It's pretty easy to implement, and works quite well. 它很容易实现,并且效果很好。

Here's an example I whipped up a while ago: GIST . 这是我前段时间举过的一个例子: GIST

Here's the code copy/pasted into one block: 这是将代码复制/粘贴到一个块中的代码:

Expression Definitions: 表达式定义:

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

The stack (really simple implementation): 堆栈(非常简单的实现):

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

}

And finally, the executor class: 最后,执行者类:

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

}

It works by first tokenizing the input (based on word boundary, and tokens). 它通过首先标记输入(基于单词边界和标记)来工作。 Then, it runs the Shunting Yard algorithm on it to convert the input into a RPN (Reverse Polish Notation) stack. 然后,它在其上运行Shunting Yard算法,以将输入转换为RPN(反向波兰表示法)堆栈。 Then, it's just a matter of executing the stack. 然后,只需执行堆栈即可。 Here's a quick example: 这是一个简单的示例:

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

Now, this example is significantly more complex than you may need. 现在,此示例比您可能需要的复杂得多。 The reason is that it also handles grouping and operator precedence. 原因是它还处理分组和运算符优先级。 But it's a decent example of a running algorithm that doesn't use EVAL and supports variables... 但这是运行算法的一个很好的例子,它不使用EVAL并支持变量...

There is a Math Parser class called bcParserPHP that might be of interest. 可能有一个名为bcParserPHP的Math Parser类。

Seems fairly simple to use and pretty powerful. 似乎相当简单易用,功能强大。

Example code from their site: 来自其站点的示例代码:

$parser = new MathParser();
$parser->setVariable('X', 5);
$parser->setVariable('Y', 2);
$parser->setExpression('COS(X)+SIN(Y)/2');
echo $parser->getValue();

Unfortunately, it's a commercial product; 不幸的是,这是一种商业产品。 I don't know if that would stop you using it or not (guess it depends on the price and on your needs). 我不知道这是否会阻止您使用它(猜测它取决于价格和您的需求)。

A non-commercial alternative might be this one: http://www.phpclasses.org/package/2695-PHP-Safely-evaluate-mathematical-expressions.html 一种非商业的替代方法可能是: http : //www.phpclasses.org/package/2695-PHP-Safely-evaluate-mathematical-expressions.html

Note that this class uses eval() internally, which I would avoid doing if possible. 请注意,此类在内部使用eval() ,如果可能的话,我会避免这样做。

Failing that, writing your own language parser would be the ideal solution, but not really sensible to do that in PHP. 失败的话,编写自己的语言解析器将是理想的解决方案,但在PHP中做到这一点并不明智。

I'd start by stripping the input of anything which shouldn't be in the expression (assuming you just want to allow add, subtract, multiply, divide, and no variables): 我将首先剥离表达式中不应该包含的任何内容(假设您只想允许加,减,乘,除,无变量):

 $expr = preg_replace('/[^0-9+*\/-]/', '', $expr);

and then, once I'm confident nothing dangerous remains in the user input, simply pass the itthrough eval() to evaluate the expression: 然后,一旦我确信用户输入中没有任何危险,只需将其传递给eval()来评估表达式:

 $result = eval("return $expr;");

No need to reinvent the wheel. 无需重新发明轮子。

Edited to incorporate Kolink's corrections. 编辑以合并Kolink的更正。 Thanks! 谢谢!

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

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