【问题标题】:Writing a simple equation parser编写一个简单的方程解析器
【发布时间】:2011-06-02 17:10:38
【问题描述】:

会使用什么样的算法来做到这一点(例如,这是一个字符串,我想找到答案):

((5 + (3 + (7 * 2))) - (8 * 9)) / 72

假设有人写了,我怎么能处理这么多嵌套的括号?

【问题讨论】:

标签: c++ algorithm parsing


【解决方案1】:

你可以使用Shunting yard algorithmReverse Polish Notation,他们都使用堆栈来处理这个,wiki说得比我好。

来自维基,

While there are tokens to be read:

    Read a token.
    If the token is a number, then add it to the output queue.
    If the token is a function token, then push it onto the stack.
    If the token is a function argument separator (e.g., a comma):

        Until the token at the top of the stack is a left parenthesis, pop operators off the stack onto the output queue. If no left parentheses are encountered, either the separator was misplaced or parentheses were mismatched.

    If the token is an operator, o1, then:

        while there is an operator token, o2, at the top of the stack, and

                either o1 is left-associative and its precedence is less than or equal to that of o2,
                or o1 is right-associative and its precedence is less than that of o2,

            pop o2 off the stack, onto the output queue;

        push o1 onto the stack.

    If the token is a left parenthesis, then push it onto the stack.
    If the token is a right parenthesis:

        Until the token at the top of the stack is a left parenthesis, pop operators off the stack onto the output queue.
        Pop the left parenthesis from the stack, but not onto the output queue.
        If the token at the top of the stack is a function token, pop it onto the output queue.
        If the stack runs out without finding a left parenthesis, then there are mismatched parentheses.

When there are no more tokens to read:

    While there are still operator tokens in the stack:

        If the operator token on the top of the stack is a parenthesis, then there are mismatched parentheses.
        Pop the operator onto the output queue.

Exit.
【解决方案2】:

解决这个问题的最简单方法是实现 Shutting Yard 算法,将表达式从中缀表示法转换为后缀表示法。

使用大写字母 E 可以轻松计算后缀表达式。

Shunting Yard 算法可以用不到 30 行代码来实现。您还需要对输入进行标记(将字符串转换为操作数、运算符和标点符号的序列),但编写一个简单的状态机来执行此操作很简单。

【讨论】:

    【解决方案3】:

    詹姆斯提供了一个很好的答案。维基百科也有一篇很好的文章。

    如果(我不建议这样做)您想直接解析该表达式,因为它看起来很有序,因为每组括号不超过一对操作数,我认为您可以这样处理它:

    解析到第一个“)”。然后解析回之前的“(”。评估里面的内容并用一个值替换整个集合。然后递归重复直到完成。

    因此,在本例中,您将首先解析“(7 * 2)”并将其替换为 14。 然后你会得到 (3 + 14) 并将其替换为 17。 等等。

    您可以使用 Regex 甚至 .IndexOf 和 .Substring 来做到这一点。

    我不会在这里检查我的语法,但是像这样:

    int y = string.IndexOf(")");  
    int x = string.Substring(0,y).LastIndexOf("(");  
    string z = string.Substring(x+1,y-x-1) // This should result in "7 * 2"
    

    您需要计算结果表达式并循环它直到括号用完,然后计算字符串的最后一部分。

    【讨论】:

    • 那么,如果您喜欢 James 的回答,为什么不点赞呢? ;-)
    • 因为我昨天才开始投稿,还没有要求的 15 个代表,否则我会有。你当然可以帮忙:)
    【解决方案4】:

    您可以使用状态机解析器(yacc LALR 等)或递归下降解析器。

    解析器可以发出 RPN 令牌以供以后评估或编译。或者,在即时解释器实现中,递归下降解析器可以在从叶标记返回时动态计算子表达式,并最终得到结果。

    【讨论】:

      【解决方案5】:

      我会使用几乎随处可用的工具。
      我喜欢 lex/yacc,因为我认识它们,但到处都有等价物。因此,在您编写复杂代码之前,请先看看是否有工具可以帮助您简化代码(此类问题之前已经解决,所以不要重新发明轮子)。

      所以,我会使用 lex(flex)/yacc(bison):

      e.l

      %option noyywrap
      
      Number      [0-9]+
      WhiteSpace  [ \t\v\r]+
      NewLine     \n
      %{
      #include <stdio.h>
      %}
      
      %%
      
      \(              return '(';
      \)              return ')';
      \+              return '+';
      \-              return '-';
      \*              return '*';
      \/              return '/';
      
      {Number}        return 'N';
      {NewLine}       return '\n';
      {WhiteSpace}    /* Ignore */
      
      .               fprintf(stdout,"Error\n");exit(1);
      
      
      %%
      

      e.y

      %{
      #include <stdio.h>
          typedef double (*Operator)(double,double);
          double mulOp(double l,double r)  {return l*r;}
          double divOp(double l,double r)  {return l/r;}
          double addOp(double l,double r)  {return l+r;}
          double subOp(double l,double r)  {return l-r;}
      extern char* yytext;
      extern void yyerror(char const * msg);
      %}
      
      %union          
      {
          Operator        op;
          double          value;
      }
      
      %type   <op>        MultOp AddOp
      %type   <value>     Expression MultExpr AddExpr BraceExpr
      
      %%
      
      Value:          Expression '\n'   { fprintf(stdout, "Result: %le\n", $1);return 0; }
      
      Expression:     AddExpr                          { $$ = $1;}
      
      AddExpr:        MultExpr                         { $$ = $1;}
                  |   AddExpr   AddOp  MultExpr        { $$ = ($2)($1, $3);}
      
      MultExpr:       BraceExpr                        { $$ = $1;}
                  |   MultExpr  MultOp BraceExpr       { $$ = ($2)($1, $3);}
      
      BraceExpr:      '(' Expression ')'               { $$ = $2;}
                  |   'N'                              { sscanf(yytext,"%le", &$$);}
      
      MultOp:         '*'                              { $$ = &mulOp;}
                  |   '/'                              { $$ = &divOp;}
      AddOp:          '+'                              { $$ = &addOp;}
                  |   '-'                              { $$ = &subOp;}
      %%
      
      void yyerror(char const * msg)
      {
          fprintf(stdout,"Error: %s", msg);
      }
       
      int main()
      {
          yyparse();
      }
      

      构建

      > flex e.l
      > bison e.y
      > gcc *.c
      > ./a.out
      ((5 + (3 + (7 * 2))) - (8 * 9)) / 72
      Result: -6.944444e-01
      >
      

      上面也处理正常的运算符优先规则:
      不是因为我做了什么,而是很久以前有人聪明地解决了这个问题,现在您可以轻松获得表达式解析的语法规则(只需 google C Grammer 并撕掉您需要的部分)。

      > ./a.out
      2 + 3 * 4
      Result: 1.400000e+01
      

      【讨论】:

        【解决方案6】:

        首先将表达式转换为前缀或后缀形式。那么它很容易评估!

        例子:

        Postfix expression evaluation.

        【讨论】:

          【解决方案7】:

          如果已知表达式是全括号的(也就是说,所有可能的括号都在那里),那么这可以很容易地使用递归下降解析来完成。本质上,每个表达式都是以下任一形式

           number
          

          或形式

           (expression operator expression)
          

          这两种情况可以通过它们的第一个标记来区分,因此简单的递归下降就足够了。实际上,我已经看到了这个确切的问题,作为在介绍性编程课程中测试递归思维的一种方式。

          如果您不一定有这个保证,那么某种形式的优先解析可能是个好主意。此问题的许多其他答案都讨论了执行此操作的各种算法。

          【讨论】:

            【解决方案8】:

            什么?不。除非这是家庭作业,否则不要编写解析器。那里有一百个解析器,它们都比这里的所有建议都有一个优势:它们已经在那里了。您不必编写它们。

            【讨论】:

              【解决方案9】:

              是的,算法是Shunting yard algorithm,但如果你想实现我建议你使用python,它是compiler package

              import compiler
              equation = "((5 + (3 + (7 * 2))) - (8 * 9)) / 72"
              parsed = compiler.parse( equation )
              
              print parsed
              

              您也可以使用built-in eval() method 评估此表达式

              print eval("5 + (4./3) * 9") // 17
              

              【讨论】:

                【解决方案10】:

                或者您可以在 R 中的一行中执行此操作:

                > eval(parse(text = '((5 + (3 + (7*2))) - (8 * 9))/72' ))
                [1] -0.6944444
                

                【讨论】:

                  猜你喜欢
                  • 2012-10-29
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2011-10-25
                  • 2011-09-08
                  • 2010-09-28
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多