如何解析涉及括号的数学表达式

时间:2010-06-03 20:34:39

标签: .net math parsing

这不是学校作业或任何事情,但我意识到这是一个主要的学术问题。但是,我一直在努力做的是解析“数学”文本并提出答案。

例如 - 我可以弄清楚如何解析'5 + 5'或'3 * 5' - 但是当我尝试正确地将操作链接在一起时我失败了。

(5 + 5)* 3

我大多只是在烦扰我,我无法弄明白。如果有人能指出我的方向,我真的很感激。

修改 感谢所有快速回复。对不起,我没有更好地解释。

首先 - 我没有使用正则表达式。我也知道已有的库可以作为字符串使用数学表达式并返回正确的值。所以,我主要是看着这个,因为,遗憾的是,我并没有“明白”。

第二 - 我尝试做过的事情(可能是误入歧途)但是我正在计算'('和')'并首先评估最深的项目。在简单的例子中,这有效;但我的代码不漂亮,更复杂的东西崩溃。当我'计算'最低级别时,我正在修改字符串。

所以... (5 + 5)* 3

会变成 10 * 3

然后评估为 30

但它感觉'错了'。

我希望这有助于澄清事情。我肯定会查看提供的链接。

13 个答案:

答案 0 :(得分:9)

在使用简单的图形应用程序时,我使用this algorithm(相当容易理解并且非常适合这些简单的数学表达式)首先将表达式转换为RPN然后计算结果。 RPN对于不同的变量值执行起来非常好而且快速。

当然,语言解析是一个非常广泛的主题,还有许多其他的方法(以及它的预制工具)

答案 1 :(得分:4)

@Rising Star [我希望将此添加为评论,但格式化失败]

这似乎违反直觉,但二叉树更简单,更灵活。在这种情况下,节点可以是常量(数字)或运算符。当您决定使用控制流和函数等元素扩展语言时,二叉树会使生活变得更容易。

示例:

((3 + 4 - 1) * 5 + 6 * -7) / 2

                  '/'
                /     \
              +        2
           /     \
         *         *
       /   \     /   \
      -     5   6     -7
    /   \
   +     1
 /   \
3     4

在上述情况下,扫描仪已编程为读取“ - ”后跟一系列数字作为单个数字,因此“-7”将作为“数字”标记的值组件返回。 ' - '后跟空格被撤回为“减号”令牌。这使得解析器更容易编写。它在你想要“ - (x * y)”的情况下失败,但你可以很容易地将表达式改为“0 - exp”

答案 2 :(得分:3)

这是一个简单的(天真的运算符优先级)语法,可以满足您的需求。

expression = 
    term
    | expression "+" term
    | expression "-" term .
term = 
    factor
    | term "*" factor
    | term "/" factor .
factor = 
    number
    | "(" expression ")" .

当您处理“因子”时,您只需检查下一个标记是否为数字,或“(”,如果它是“(”那么您再解析“表达式”,当表达式返回时检查如果下一个标记是“)”。您可以通过使用 out ref 参数将[calculate | read]值冒泡到父级,或构建一个表达树。

以下是EBNF中的相同内容:

expression = 
    term
    { "+" term | "-" term  } .

term = 
    factor
    { "*" factor | "/" factor }.

factor = 
    number
    | "(" expression ")" .

答案 3 :(得分:2)

你有没有在学校上过正规语言课?实际上你需要一个语法来解析。

编辑:哦,废话,维基百科说我错了,但现在我忘了正确的名字:( http://en.wikipedia.org/wiki/Formal_grammar

答案 4 :(得分:2)

去年,我写了一个基本的数学评估员,原因是我不记得了。在任何一段时间内,它都不是一个“适当的”解析器,并且......就像所有旧代码一样,我现在并不为它感到骄傲。

但是you can take a look,看看它是否对你有帮助。

通过启动此standalone Java app

运行一些输入测试

答案 5 :(得分:2)

当我想解析某些内容时,我决定使用GOLD Parser:

  • 独立文档(不需要书籍来理解)
  • 各种运行时引擎,包括我想要的各种编程语言。

解析器包含sample grammars,包括例如一个用于操作员优先。


除了GOLD之外,还有其他更着名的解析器,例如ANTLR,我还没有用过。

答案 6 :(得分:2)

答案 7 :(得分:2)

正如许多答案已经说明的那样,问题是您需要recursive parser associativity rules,因为您最终可能会遇到以下表达式:

val = (2-(2+4+(3-2)))/(2+1)*(2-1)

并且您的解析器需要知道:

  1. 括号表达式由内而外评估
  2. 除法优先于乘法(先划分,然后乘以结果)
  3. 乘法优先于加法/减法
  4. 你可以想象,写一个(好的)解析器是一门艺术。好消息是有几个工具,称为parser generators,可以让您轻松定义语言的语法,以及解析规则。您可能需要检查维基百科中 BNF 的条目,以便了解如何定义语法。

    最后,如果你这样做是为了学习经验,请继续。如果这是用于生产代码,请不要重新发明轮子,并找到现有的库,否则您可能会花费1000行代码来添加2​​ + 2。

答案 8 :(得分:1)

基本上,你问我们如何写一个“解析器”。这是关于解析器的另一个Stack Overflow问题:hand coding a parser

答案 9 :(得分:1)

我做了类似你描述的事情。我使用递归来解析所有括号。然后我使用三元树来表示不同的段。左侧分支是操作员的左侧。中心分支是运营商。右分支是运营商的右侧。

简短回答递归和三元树。

答案 10 :(得分:1)

总有一个选项可以使用数学解析器库,例如mXparser。你可以:

1 - 检查表达式语法

import org.mariuszgromada.math.mxparser.*;
...
...
Expression e = new Expression("2+3-");
e.checkSyntax();
mXparser.consolePrintln(e.getErrorMessage());

结果:

[mXparser-v.4.0.0] [2+3-] checking ...
[2+3-] lexical error 

Encountered "<EOF>" at line 1, column 4.
Was expecting one of:
    "(" ...
    "+" ...
    "-" ...
    <UNIT> ...
    "~" ...
    "@~" ...
    <NUMBER_CONSTANT> ...
    <IDENTIFIER> ...
    <FUNCTION> ...
    "[" ...

[2+3-] errors were found.

[mXparser-v.4.0.0]

2 - 评估表达

import org.mariuszgromada.math.mxparser.*;
...
...
Expression e = new Expression("2+3-(10+2)");
mXparser.consolePrintln(e.getExpressionString() + " = " + e.calculate());

结果:

[mXparser-v.4.0.0] 2+3-(10+2) = -7.0

3 - 使用内置函数常量,运算符等。

import org.mariuszgromada.math.mxparser.*;
...
...
Expression e = new Expression("sin(pi)+e");
mXparser.consolePrintln(e.getExpressionString() + " = " + e.calculate());

结果:

[mXparser-v.4.0.0] sin(pi)+e = 2.718281828459045

4 - 定义自己的函数,参数和常量

import org.mariuszgromada.math.mxparser.*;
...
...
Argument z = new Argument("z = 10");
Constant a = new Constant("b = 2");
Function p = new Function("p(a,h) = a*h/2");
Expression e = new Expression("p(10, 2)-z*b/2", p, z, a);
mXparser.consolePrintln(e.getExpressionString() + " = " + e.calculate());

结果:

[mXparser-v.4.0.0] p(10, 2)-z*b/2 = 0.0

5 - Tokenize表达式字符串并使用表达式标记

import org.mariuszgromada.math.mxparser.*;
...
...
Argument x = new Argument("x");
Argument y = new Argument("y");
Expression e = new Expression("2*sin(x)+(3/cos(y)-e^(sin(x)+y))+10", x, y);
mXparser.consolePrintTokens( e.getCopyOfInitialTokens() );

结果:

[mXparser-v.4.0.0]  --------------------
[mXparser-v.4.0.0] | Expression tokens: |
[mXparser-v.4.0.0]  ---------------------------------------------------------------------------------------------------------------
[mXparser-v.4.0.0] |    TokenIdx |       Token |        KeyW |     TokenId | TokenTypeId |  TokenLevel |  TokenValue |   LooksLike |
[mXparser-v.4.0.0]  ---------------------------------------------------------------------------------------------------------------
[mXparser-v.4.0.0] |           0 |           2 |       _num_ |           1 |           0 |           0 |         2.0 |             |
[mXparser-v.4.0.0] |           1 |           * |           * |           3 |           1 |           0 |         NaN |             |
[mXparser-v.4.0.0] |           2 |         sin |         sin |           1 |           4 |           1 |         NaN |             |
[mXparser-v.4.0.0] |           3 |           ( |           ( |           1 |          20 |           2 |         NaN |             |
[mXparser-v.4.0.0] |           4 |           x |           x |           0 |         101 |           2 |         NaN |             |
[mXparser-v.4.0.0] |           5 |           ) |           ) |           2 |          20 |           2 |         NaN |             |
[mXparser-v.4.0.0] |           6 |           + |           + |           1 |           1 |           0 |         NaN |             |
[mXparser-v.4.0.0] |           7 |           ( |           ( |           1 |          20 |           1 |         NaN |             |
[mXparser-v.4.0.0] |           8 |           3 |       _num_ |           1 |           0 |           1 |         3.0 |             |
[mXparser-v.4.0.0] |           9 |           / |           / |           4 |           1 |           1 |         NaN |             |
[mXparser-v.4.0.0] |          10 |         cos |         cos |           2 |           4 |           2 |         NaN |             |
[mXparser-v.4.0.0] |          11 |           ( |           ( |           1 |          20 |           3 |         NaN |             |
[mXparser-v.4.0.0] |          12 |           y |           y |           1 |         101 |           3 |         NaN |             |
[mXparser-v.4.0.0] |          13 |           ) |           ) |           2 |          20 |           3 |         NaN |             |
[mXparser-v.4.0.0] |          14 |           - |           - |           2 |           1 |           1 |         NaN |             |
[mXparser-v.4.0.0] |          15 |           e |           e |           2 |           9 |           1 |         NaN |             |
[mXparser-v.4.0.0] |          16 |           ^ |           ^ |           5 |           1 |           1 |         NaN |             |
[mXparser-v.4.0.0] |          17 |           ( |           ( |           1 |          20 |           2 |         NaN |             |
[mXparser-v.4.0.0] |          18 |         sin |         sin |           1 |           4 |           3 |         NaN |             |
[mXparser-v.4.0.0] |          19 |           ( |           ( |           1 |          20 |           4 |         NaN |             |
[mXparser-v.4.0.0] |          20 |           x |           x |           0 |         101 |           4 |         NaN |             |
[mXparser-v.4.0.0] |          21 |           ) |           ) |           2 |          20 |           4 |         NaN |             |
[mXparser-v.4.0.0] |          22 |           + |           + |           1 |           1 |           2 |         NaN |             |
[mXparser-v.4.0.0] |          23 |           y |           y |           1 |         101 |           2 |         NaN |             |
[mXparser-v.4.0.0] |          24 |           ) |           ) |           2 |          20 |           2 |         NaN |             |
[mXparser-v.4.0.0] |          25 |           ) |           ) |           2 |          20 |           1 |         NaN |             |
[mXparser-v.4.0.0] |          26 |           + |           + |           1 |           1 |           0 |         NaN |             |
[mXparser-v.4.0.0] |          27 |          10 |       _num_ |           1 |           0 |           0 |        10.0 |             |
[mXparser-v.4.0.0]  ---------------------------------------------------------------------------------------------------------------

6 - 您可以在 mXparser tutorialmXparser math collectionmXparser API definition中找到更多内容。

7 - mXparser支持:

  • JAVA
  • .NET / MONO
  • .NET Core
  • .NET Standard
  • .NET PCL
  • Xamarin.Android
  • Xamarin.iOS

祝你好运

答案 11 :(得分:1)

对于任何在发表这篇文章的九年后才看到这个问题的人:如果您不想重新发明轮子,那么那里就有许多奇特的数学解析器。

我几年前用Java写过一篇文章,它支持算术运算,方程求解,微积分,积分微积分,基本统计量,函数/公式定义,图形等。

它叫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

此解析器非常快,并且具有许多其他功能。

免责声明:ParserNG是我创作的。

答案 12 :(得分:0)

取自 here [但是,我已经向其中添加了 Division(/) 功能]

<html>
<body>
    <h1>how to write a parser - part2 </h1>
    Expression<input id='expression'>
    Result<input id='result'>
    <button onclick="parse()">PARSE</button>
</body>
<script>
    // split expression by operator considering parentheses
    const split = (expression, operator) => {
        const result = [];
        let braces = 0;
        let currentChunk = "";
        for (let i = 0; i < expression.length; ++i) {
            const curCh = expression[i];
            if (curCh == '(') {
                braces++;
            } else if (curCh == ')') {
                braces--;
            }
            if (braces == 0 && operator == curCh) {
                result.push(currentChunk);
                currentChunk = "";
            } else currentChunk += curCh;
        }
        if (currentChunk != "") {
            result.push(currentChunk);
        }
        return result;
    };
// Division
    const parseDivisionSeparatedExpression = (expression) => {
        const numbersString = split(expression, '/');
        const numbers = numbersString.map(noStr => {
            if (noStr[0] == '(') {
                const expr = noStr.substr(1, noStr.length - 2);
                // recursive call to the main function
                return parsePlusSeparatedExpression(expr);
            }
            return noStr;
        });
        const initialValue = 1.0;
        const result = numbers.reduce((acc, no) => {
            return acc / no
        });
        return result;
    };
    var res = (12 - 5-(5/2 + (32 + 4)) + (3*20))/2//12-5-38.5+60
    // this will only take strings containing * operator [ no + ]
    const parseMultiplicationSeparatedExpression = (expression) => {
        const numbersString = split(expression, '*');
        const numbers = numbersString.map(noStr => parseDivisionSeparatedExpression(noStr));

        const initialValue = 1.0;
        console.log("parseMultiplicationSeparatedExpression - numbers: ", numbers)
        const result = numbers.reduce((acc, no) => acc * no, initialValue);
        return result;
    };
    // both * -
    const parseMinusSeparatedExpression = (expression) => {
        const numbersString = split(expression, '-');
        const numbers = numbersString.map(noStr => parseMultiplicationSeparatedExpression(noStr));
        const initialValue = numbers[0];
        const result = numbers.slice(1).reduce((acc, no) => acc - no, initialValue);
        return result;
    };
    // * - +
    const parsePlusSeparatedExpression = (expression) => {
        const numbersString = split(expression, '+');
        const numbers = numbersString.map(noStr => parseMinusSeparatedExpression(noStr));
        const initialValue = 0.0;
        const result = numbers.reduce((acc, no) => acc + no, initialValue);
        return result;
    };
    const parse = () => {
        const expressionNode = document.getElementById('expression');
        const resultNode = document.getElementById('result');
        var expression = expressionNode.value;
        expression = expression.replace(/ +/g, '')
        console.log("parse - expression: ", expression)
        const result = parsePlusSeparatedExpression(expression, '+');
        resultNode.value = String(result);
    };
</script>

示例: 12 * 5+(5 * (32 - 4)) + 3 = 203