【问题标题】:How to make a calculator in PHP?如何用 PHP 制作计算器?
【发布时间】:2012-09-23 10:48:36
【问题描述】:

我想使用 PHP 计算简单的代数表达式,例如 8*(5+1),普通用户通过 <input> 标签输入(这意味着,普通表示法:没有像 @987654325 这样的语法变化@. 另外,它必须显示所有步骤,但这并不难。现在的问题是计算表达式的值。

注意:这是我目前的想法,效率很低,但它是一个临时解决方案。 尽可能替换字符串:在我们的示例中,识别字符串 5+1 并将其替换为 6。然后,再次循环,将(6) 替换为6,再次循环,并将8*6 替换为48。 例如,乘法代码应如下所示:

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

【问题讨论】:

  • 首先想到的是使用堆栈来推送操作,然后他们写下您用来决定首先执行哪些操作的操作规则的顺序。这将帮助您将它们转换为代码。
  • google "Djikstra shutting yard" 算法,或者看看phpclasses上的evalmath库
  • 作为参考,Mac 键盘上的反引号位于 Z 旁边
  • 使用正则表达式进行断言,然后只需 eval 它。这就是它的用途,尽管专业人士喜欢更复杂的方法,是的,新手通常应该回避。

标签: php


【解决方案1】:

我会首先去除不应该出现在表达式中的任何输入(假设您只想允许加、减、乘、除,并且没有变量):

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

然后,一旦我确信用户输入中没有任何危险,只需通过 eval() 传递它来评估表达式:

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

无需重新发明轮子。

已编辑以纳入 Kolink 的更正。谢谢!

【讨论】:

  • eval 不会返回任何内容,除非其中有 return 语句——这与 JavaScript 中的返回值是最后一个表达式的值不同。另外,不要忘记对用作分隔符的字符进行转义。
  • 感谢您的更正——我已经更新了答案。这会教我在出去抽烟时用手机接听...
  • 好吧,这不是一件好事,因为如果有语法错误(例如,如果我输入了0 + 1 1)它会破坏整个系统......更好地使用一个成熟的解析器...
【解决方案2】:

有一个名为bcParserPHP 的数学解析器类可能很有趣。

看起来相当简单易用且功能强大。

来自他们网站的示例代码:

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

不幸的是,它是一种商业产品;我不知道这是否会阻止您使用它(我猜这取决于价格和您的需求)。

非商业替代品可能是这个:http://www.phpclasses.org/package/2695-PHP-Safely-evaluate-mathematical-expressions.html

请注意,此类在内部使用eval(),如果可能,我会避免这样做。

如果做不到这一点,编写自己的语言解析器将是理想的解决方案,但在 PHP 中这样做并不明智。

【讨论】:

  • 在 evalmath 中使用 eval() 可以很容易地避免,只需稍加修改或修改代码以将其替换为列入白名单的函数集,并使用 call_user_func_array()
【解决方案3】:

根据您的需要,我建议您查看Shunting Yard Algorithm。它很容易实现,而且效果很好。

这是我不久前整理的一个示例:GIST

这是代码复制/粘贴到一个块中:

表达式定义:

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

}

它首先对输入进行标记(基于单词边界和标记)。然后,它在其上运行 Shutting Yard 算法以将输入转换为 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)

现在,这个示例比您可能需要的要复杂得多。原因是它还处理分组和运算符优先级。但这是不使用 EVAL 并支持变量的运行算法的一个不错的例子......

【讨论】:

  • 它非常适合我的需求,尽管它似乎会在使用变量时出错。确切地说:致命错误:在 /index.php:122 中未捕获的异常“异常”和消息“未定义值 a” 堆栈跟踪:#0 /index.php(177): TerminalExpression::factory('a') #1 /index .php(167): Math->parse('($a + 3) * 4') #2 /index.php(290): Math->evaluate('($a + 3) * 4') #3 {main} 在第 122 行的 /index.php 中抛出
  • 我遇到了换行问题,所以我做了一些不成功的编辑。反正。它错过了我需要的关键功能之一,即逐步解决。我可以尝试使用带括号的explode(),但这仍然不是解决方案。
  • @GiulioMuscarello:我修复了标记器正则表达式的问题。它现在可以正确处理换行符和变量。只需替换 Math 类的最后一个方法(因为它具有更正的正则表达式)...
  • This lecture 应作为相关材料提及。
  • @ircmaxell 现在代码可以完美运行,谢谢。您想被引用,还是需要注明出处?
猜你喜欢
  • 2012-08-05
  • 1970-01-01
  • 2019-05-11
  • 2021-12-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多