一个智能的数学解析器的设计?

63

什么是设计数学解析器的最聪明方式?我指的是一个函数,它接受一个数学字符串(如:"2 + 3 / 2 + (2 * 5)"),并返回计算后的值。我很久以前在VB6中写过一个,但它最终变得过于臃肿,而且不太可移植(或聪明...)。欢迎提供一般性想法、伪代码或实际代码。


你可以在这里查看Shunting Yard算法的Java实现:http://projects.congrace.de/exp4j/ - fasseg
请查看我在 https://dev59.com/zHVD5IYBdhLWcg3wR5ko#46722767 发布的代码。 - user4617883
10个回答

90

1
我认为逆波兰算法无法正确处理一元负数与指数基数的绑定。也就是说,它无法将-2^3解析为-(2^3),只能解析为(-2)^3。但我可能是错的,我不确定。 - chbaker0
3
我认为那是用户输入错误,我不会费心去纠正它。如果我感觉友好的话,我可能会添加一些检查内容,并提醒用户他们的输入可能有误。 - Yay295

13

6
你有两种方法。你可以生成动态代码并执行它,以便在不需要编写太多代码的情况下得到答案。只需在.NET中运行时生成的代码上进行搜索,就会有很多示例。
另外,你可以创建一个实际的解析器,并生成一个小的解析树,然后用它来评估表达式。对于基本表达式来说,这也是相当简单的。请查看codeplex,因为我相信他们在那里有一个数学解析器。或者只需查找BNF,其中将包括示例。任何介绍编译器概念的网站都将将其作为基本示例。 Codeplex Expression Evaluator

4

我知道这篇文章比较旧,但是在我开发一个大型应用程序中的计算器时,我遇到了一些问题,并尝试使用被接受的答案。这些链接对于理解和解决这个问题非常有帮助,因此不应该被忽视。 我正在使用Java编写Android应用程序,并为"string"表达式中的每个项目实际上将一个String存储在ArrayList中,当用户在小键盘上输入时。 对于中缀转后缀的转换,我迭代了ArrayList中的每个String,然后计算新排列的后缀ArrayList中的值。 对于少量操作数/运算符而言,这非常棒,但长时间的计算结果往往不准确,特别是当表达式开始评估为非整数时。 在中缀转后缀转换提供的链接中,它建议如果扫描的项是运算符且topStack项具有更高的优先级,则弹出堆栈。 我发现这几乎是正确的。如果顶部堆栈项的优先级高于或等于扫描的运算符,则弹出topStack项最终使我的计算结果正确。 希望这能帮助正在解决这个问题的任何人,并感谢Justin Poliey(以及fas?)提供了一些宝贵的链接。


4
如果你有一个“始终开启”的应用程序,只需将数学字符串发布到Google并解析结果。这是一种简单的方法,但不确定是否适合你的需求 - 但在某些方面很聪明。

2
这就是我的意思,你很聪明,知道如何完成任务;-) - JRoppert

3

1

开发者总是希望采用简洁的方法,并尝试从头开始实现解析逻辑,通常最终会采用Dijkstra Shunting-Yard Algorithm。结果是干净漂亮的代码,但可能存在漏洞。我已经开发了这样一个API,JMEP,它可以完成所有这些工作,但花费我多年时间才有稳定的代码。

即使经过那么多工作,你仍然可以看到甚至在那个项目页面上,我也在认真考虑转而使用JavaCC或ANTLR,即使已经完成了所有这些工作。


1

ANTLR是一个非常好的LL(*)解析器生成器。我强烈推荐它。


1
假设您的输入是字符串格式的中缀表达式,您可以将其转换为后缀表达式,然后使用一对栈:一个操作符栈和一个操作数栈,从那里开始解决问题。您可以在维基百科链接中找到通用算法信息。

1

距离这个问题被提出已经过去了11年:如果您不想重复造轮子,那么有许多异国情调的数学解析器可供选择。

我多年前写了一个支持算术运算、方程求解、微积分、统计学基础、函数/公式定义、绘图等功能的解析器。

它名为ParserNG,并且是免费的。

评估表达式就像这样简单:

    MathExpression expr = new MathExpression("(34+32)-44/(8+9(3+2))-22"); 
    System.out.println("result: " + expr.solve());

    result: 43.16981132075472

或者使用变量和计算简单表达式:

 MathExpression expr = new MathExpression("r=3;P=2*pi*r;"); 
System.out.println("result: " + expr.getValue("P"));

或者使用函数:

MathExpression expr = new MathExpression("f(x)=39*sin(x^2)+x^3*cos(x);f(3)"); 
System.out.println("result: " + expr.solve());

result: -10.65717648378352

或者在给定点处求导数(注意它在幕后进行符号微分(而不是数值微分),因此精度不受数值逼近误差的限制):

MathExpression expr = new MathExpression("f(x)=x^3*ln(x); diff(f,3,1)"); 
System.out.println("result: " + expr.solve());

 result: 38.66253179403897

我将为您翻译内容,这是关于编程的。在x=3处对x^3 * ln(x)进行一次求导。现在可以进行的求导次数为1。

或者针对数值积分:

MathExpression expr = new MathExpression("f(x)=2*x; intg(f,1,3)"); 
System.out.println("result: " + expr.solve());

result: 7.999999999998261... approx: 8

这个解析器速度相当快,而且有许多其他功能。
我们已经通过Objective C绑定将其移植到Swift,并在图形应用程序等迭代使用案例中使用了它。
免责声明:ParserNG由我编写。

这个有什么优势比NCalc更好吗?https://github.com/ncalc/ncalc - intrepidis
你需要查看两者的文档,以确定哪一个具有更多的功能,特别是哪一个具有你所需的功能。我谈论的是ParserNG,它拥有许多功能,可能还会有更多的功能。除了表达式求值外,它还可以用于变量创建、函数创建、方程求解、矩阵和统计函数、微分和积分等方面。 - gbenroscience

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接