我正在用PHP创建一个CAS(计算机代数系统),但我现在卡住了。我正在使用this website。
现在我写了一个tokenizer。它将转换如下的等式:
1+2x-3*(4-5*(3x))
到此:
NUMBER PLUS_OPERATOR NUMBER VAR[X] MINUS_OPERATOR NUMBER MULTIPLY_OPERATOR GROUP
(其中group是另一组令牌)。我该如何简化这个等式?是的,我知道你能做什么:添加X-vars,但它们在子组中。我可以用来处理这些令牌的最佳方法是什么?
答案 0 :(得分:19)
真正有用的下一步是构建一个解析树:
您可以通过编写中缀解析器来制作其中一个。你可以通过编写一个简单的递归下降解析器,或引入大枪和using a parser generator来做到这一点。在任何一种情况下,它都有助于构建一个正式的语法:
expression: additive
additive: multiplicative ([+-] multiplicative)*
multiplicative: primary ('*' primary)*
primary: variable
| number
| '(' expression ')'
请注意,此语法不处理2x
语法,但应该很容易添加。
请注意在语法规则中巧妙使用递归。 primary
仅捕获变量,数字和带括号的表达式,并在运行到运算符时停止。 multiplicative
解析由primary
个符号分隔的一个或多个*
个词组,但在遇到+
或-
个符号时停止。 additive
解析由multiplicative
和+
分隔的一个或多个-
个词组,但在遇到)
时停止。因此,递归方案确定运算符优先级。
手动实施predictive parser并不是非常困难,正如我在下面所做的那样(see full example at 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 + ...
我会把那部分留给你。使用解析树应该会使事情变得更加直接。
答案 1 :(得分:3)
PHP擅长字符串,数字和数组。但它实现符号公式操作是一种糟糕的语言,因为它没有处理“符号表达式”的原生机制,你真正想要树。是的,你可以实现所有这些机器。更难的是进行代数操作。如果你想要构建一些半复杂的东西,它需要做很多工作。理想情况下,您希望机器能够帮助您直接轻松地编写转换。
例如,您将如何实现任意代数规则?相关性和交换性?术语“在远处匹配”?,例如
(3*a+b)-2(a-b)+a ==> 3a-b
您可以查看a simple CAS can be implemented使用DMS program transformation system的方式。 DMS具有内置的交换性和关联性等硬数学结构,您可以明确地编写代数规则来操作符号公式。
答案 2 :(得分:1)
这本书 Computer Algebra and Symbolic Computation: Mathematical Methods by Joel S. Cohen 描述了一种自动简化代数表达式的算法。
此算法用于C#的Symbolism计算机代数库。继续你的例子,下面的C#程序:
var x = new Symbol("x");
(1 + 2 * x - 3 * (4 - 5 * (3 * x)))
.AlgebraicExpand()
.Disp();
在控制台显示以下内容:
-11 + 47 * x