13

我正在用 PHP 创建一个 CAS(计算机代数系统),但我现在被卡住了。我正在使用这个网站

现在我写了一个分词器。它将转换如下方程:

1+2x-3*(4-5*(3x))

对此:

NUMBER PLUS_OPERATOR NUMBER VAR[X] MINUS_OPERATOR NUMBER MULTIPLY_OPERATOR GROUP

(其中 group 是另一组令牌)。我怎样才能简化这个方程?是的,我知道你能做什么:添加 X-vars,但它们在子组中。我可以用来处理这些令牌的最佳方法是什么?

4

3 回答 3

21

一个真正有用的下一步是构建一个解析树:

在此处输入图像描述

您可以通过编写中缀解析器来实现其中之一。您可以通过编写一个简单的递归下降解析器来做到这一点,或者引入大炮并使用解析器生成器。无论哪种情况,它都有助于构建一个正式的语法:

expression: additive

additive: multiplicative ([+-] multiplicative)*

multiplicative: primary ('*' primary)*

primary: variable
       | number
       | '(' expression ')'

请注意,此语法不处理2x语法,但应该很容易添加。

注意语法规则中递归的巧妙使用。 primary仅捕获变量、数字和带括号的表达式,并在遇到运算符时停止。 multiplicative解析一个或多个primary由符号分隔的表达式*,但在遇到+or-符号时停止。 additive解析一个或多个由andmultiplicative分隔的表达式,但在遇到 a 时停止。因此,递归方案决定了运算符的优先级。+-)

正如我在下面所做的那样,手动实现预测解析器并不太难(参见 ideone.com 上的完整示例):

function parse()
{
    global $tokens;
    reset($tokens);
    $ret = parseExpression();
    if (current($tokens) !== FALSE)
        die("Stray token at end of expression\n");
    return $ret;
}

function popToken()
{
    global $tokens;
    $ret = current($tokens);
    if ($ret !== FALSE)
        next($tokens);
    return $ret;
}

function parseExpression()
{
    return parseAdditive();
}

function parseAdditive()
{
    global $tokens;

    $expr = parseMultiplicative();

    for (;;) {
        $next = current($tokens);
        if ($next !== FALSE && $next->type == "operator" &&
            ($next->op == "+" || $next->op == "-"))
        {
            next($tokens);
            $left = $expr;
            $right = parseMultiplicative();
            $expr = mkOperatorExpr($next->op, $left, $right);
        } else {
            return $expr;
        }
    }
}

function parseMultiplicative()
{
    global $tokens;

    $expr = parsePrimary();

    for (;;) {
        $next = current($tokens);
        if ($next !== FALSE && $next->type == "operator" &&
            $next->op == "*")
        {
            next($tokens);
            $left = $expr;
            $right = parsePrimary();
            $expr = mkOperatorExpr($next->op, $left, $right);
        } else {
            return $expr;
        }
    }
}

function parsePrimary()
{
    $tok = popToken();
    if ($tok === FALSE)
        die("Unexpected end of token list\n");
    if ($tok->type == "variable")
        return mkVariableExpr($tok->name);
    if ($tok->type == "number")
        return mkNumberExpr($tok->value);
    if ($tok->type == "operator" && $tok->op == "(") {
        $ret = parseExpression();
        $tok = popToken();
        if ($tok->type == "operator" && $tok->op == ")")
            return $ret;
        else
            die("Missing end parenthesis\n");
    }

    die("Unexpected $tok->type token\n");
}

好的,现在你有了这个可爱的解析树,甚至还有一张漂亮的图片。怎么办?您的目标(目前)可能是简单地组合术语以获得形式的结果:

n1*a + n2*b + n3*c + n4*d + ...

我会把那部分留给你。拥有解析树应该使事情变得更加简单。

于 2011-03-24T03:52:21.333 回答
3

PHP 擅长字符串、数字和数组。但它对于实现符号公式操作来说是一种糟糕的语言,因为它没有处理“符号表达式”的本地机制,你真的想要树。是的,您可以实现所有这些机器。更难的是进行代数运算。如果你想构建一些半复杂的东西,它会做很多工作。理想情况下,您希望机器可以帮助您直接轻松地编写转换。

例如,您将如何实现任意代数规则?结合性和交换性?术语“远距离匹配”?,例如

  (3*a+b)-2(a-b)+a ==> 3a-b

您可以了解如何使用我们的DMS 程序转换系统来实现简单的 CAS。DMS 具有内置的交换性和关联性等硬数学结构,您可以明确编写代数规则以对符号公式进行操作。

于 2011-03-23T22:59:20.417 回答
1

Joel S. Cohen的《 计算机代数和符号计算:数学方法》一书 描述了一种自动简化代数表达式的算法。

此算法用于 C# 的Symbolism计算机代数库。以您的示例为例,以下 C# 程序:

var x = new Symbol("x");

(1 + 2 * x - 3 * (4 - 5 * (3 * x)))
    .AlgebraicExpand()
    .Disp();

在控制台显示以下内容:

-11 + 47 * x
于 2013-01-02T01:02:42.100 回答