【问题标题】:How to parse mathematical expressions involving parentheses如何解析涉及括号的数学表达式
【发布时间】:2010-06-03 20:34:38
【问题描述】:

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

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

(5 + 5) * 3

我无法弄清楚这主要是困扰我。如果有人能指出我的方向,我将不胜感激。

编辑 感谢所有快速回复。很抱歉我没有更好地解释。

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

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

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

会变成 10 * 3

然后评估为 30

但只是感觉“不对”。

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

【问题讨论】:

标签: .net math parsing


【解决方案1】:

很久以前,在开发一个简单的图形应用程序时,我使用this algorithm(它相当容易理解并且非常适合此类简单的数学表达式)首先将表达式转换为RPN,然后计算结果。对于不同的变量值,RPN 执行起来既快又好。

当然,语言解析是一个非常广泛的话题,还有很多其他方法可以解决这个问题(以及为此准备的工具)

【讨论】:

  • 这是一个小型计算器解析器的好主意,并且可以快速实现。但是如果你想做稍微复杂一点的事情,比如函数调用,它也会很快变得很麻烦 (sin,cos)
  • @shoosh:实际上,函数调用可以很容易地实现为一元运算符(尽管 Wikipedia 页面似乎忽略了它们,但可以扩展算法以将它们考虑在内)。对于多个参数,您可以引入将值打包在一起的二进制逗号运算符。
  • ~ 除了高阶数学之外,还有哪些数学调用会在括号内包含多个参数?如果您正在对具有多个维度的东西进行解析器,那么有些事情告诉我您已经完成了一个更简单的解析器......另外,(简单来说)sin( VALUE ) & VALUE ::= [paren-open] term [operator term] [paren-close] 没有?
  • 你当然是对的。不过,当您希望重载相同的函数名以获取一个或两个参数时,这真是太棒了。
  • @shoosh:不,如果您认为f(x, y) 应用于单个参数,即(x, y),那么您会没事的。您可以将此参数视为一个元组,由 , 运算符创建(正如 Matti 所暗示的)。
【解决方案2】:

@新星 [我希望将其添加为评论,但格式化失败]

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

例子:

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

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

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

【讨论】:

  • 复合模式的简单应用会将内部(复合)节点视为“BinaryOperator”,将叶节点视为“Constant”。这当然不再是严格意义上的二叉树。
  • 我必须更正自己:“-”紧跟“[0-9]”变成了一个数字,“-”的任何其他实例都作为减号标记返回。返回“令牌”流的简单正则表达式将是: "(-?[0-9]+|[*+-/()]|[a-z][a-z0-9]+| =||=)”。这处理标识符和关系运算符。它将未指定的词位视为空格。
【解决方案3】:

这是你想要的简单(朴素运算符优先级)语法。

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

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

EBNF 中也是这样:

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

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

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

【讨论】:

    【解决方案4】:

    【讨论】:

    • 仅链接的答案最好放置为 cmets。
    【解决方案5】:

    您曾经在学校上过关于正式语言的课程吗?实际上,您需要一个语法来解析。

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

    【讨论】:

    • 中缀表示法需要语法。后缀(RPN)可以用下推自动机解析,比语法更容易实现。
    • 注意不要将(正式)语法与上下文无关语法等同起来。一种正则语言也是由一种文法生成的,一种正则的。
    • @anno ~ 难道基本的数学运算不能用正式的语法来描述吗?但是,是的,我最初认为它是无上下文的,还有自动机。
    【解决方案6】:

    去年我写了一个基本的数学评估器,原因我不记得了。从任何角度来看,它都不是一个“合适的”解析器,而且......就像所有旧代码一样,我现在并不为它感到骄傲。

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

    您可以通过启动此standalone Java app 来运行一些输入测试

    【讨论】:

      【解决方案7】:

      当我想解析某些东西时,我决定使用 GOLD 解析器:

      • 自成体系的文档(不需要书就能理解)
      • 各种运行时引擎,采用各种编程语言,包括我想要的那种。

      解析器包括sample grammars,包括例如一个用于运算符优先级。


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

      【讨论】:

        【解决方案8】:

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

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

        你的解析器需要知道:

        1. 括号表达式是从内向外计算的
        2. 除法优先于乘法(先除法,再乘结果)
        3. 乘法优先于加法/减法

        您可以想象,编写(好的)解析器是一门艺术。好消息是有几个工具,称为parser generators,可让您轻松定义语言的语法,以及解析规则。您可能需要在 Wikipedia 中查看 BNF 的条目,以便了解语法是如何定义的。

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

        【讨论】:

        • 我同意第 1 点和第 2 点,但 3 似乎没有必要,因为除法与乘以倒数(乘法逆)[即a / b = a * 1/b],乘法是结合的 [(a * b) * c = a * (b * c)] 和交换的 [a * b = b * a]。这些属性以及分配属性 [a * (b + c) = (a * b) + (a * c)] 通常用于优化表达式。
        • 看来我在评论中犯了一个错误:我同意 1.括号表达式是从内到外评估 3.乘法优先于加法/减法 这是我的第二项不同意(除法优先于乘法)
        • @Andre:但是如果除法不优先于乘法,你会怎么做:6/2*4?你怎么能把你没有价值的东西乘以四?你不是先得到值 6/2 = 3,然后是 3*4 = 12 吗?正如您所提到的,“除法与乘以倒数相同”,为了得到您必须除以的倒数(否则,倒数在哪里?)
        • 我认为“优先级”这个词是不正确的,因为它们具有相同的“运算符优先级”,也许是“关联性”?
        • @Arrieta:你是对的,这通常可以通过关联来解决:PLUS、MINUS、MUL 和 DIV 运算符通常是左关联的。因此 6/2*4 本质上被解析为 (6/2)*4; 4*6/2 被解析为 (4*6)/2。如果将“^”定义为指数运算符,则使用右关联(递归),例如2^3^4 --> 2^(3^4).
        【解决方案9】:

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

        有一个是我多年前用 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 由我创作。

        【讨论】:

          【解决方案10】:

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

          【讨论】:

            【解决方案11】:

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

            简答递归和三叉树。

            【讨论】:

              【解决方案12】:

              始终可以选择使用数学解析器库,例如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 - 标记表达式字符串并使用表达式标记

              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/单声道
              • .NET 核心
              • .NET 标准
              • .NET PCL
              • Xamarin.Android
              • Xamarin.iOS

              另外 - 这个软件也使用 mXparser - 你可以学习语法 Scalar Calculator app

              最好的问候

              【讨论】:

                【解决方案13】:

                取自here [但是,我已经添加了除法(/) 功能]

                <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

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2010-12-24
                  • 1970-01-01
                  • 2019-06-24
                  • 2011-02-25
                  • 1970-01-01
                  相关资源
                  最近更新 更多