编写一个简单的方程解析器

时间:2011-01-03 06:08:01

标签: c++ algorithm parsing

将使用哪种算法来实现此目的(例如,这是一个字符串,我想找到答案):

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

如果有人写道,我怎么能处理这么多嵌套的括号?

10 个答案:

答案 0 :(得分:21)

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

来自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.

答案 1 :(得分:4)

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

评估后缀表达式很容易用a-capital-E。

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

答案 2 :(得分:3)

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

如果(我不建议这样做)你想直接解析那个表达式,因为看起来有条不紊,因为每组parens都只有一对操作数,我想你可以像这样接近它:

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

因此,在此示例中,您将首先解析“(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"

你需要评估结果表达式并循环它直到parens用尽,然后评估字符串的最后部分。

答案 3 :(得分:2)

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

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

答案 4 :(得分:2)

我会使用几乎无处不在的工具 我喜欢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

答案 5 :(得分:0)

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

示例:

Postfix expression evaluation.

答案 6 :(得分:0)

如果已知表达式是完全括号的(即,所有可能的括号都在那里),那么可以使用递归下降解析轻松完成。基本上,每个表达式都是

形式
 number

或表格

 (expression operator expression)

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

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

答案 7 :(得分:0)

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

答案 8 :(得分:0)

是的,算法是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

答案 9 :(得分:-2)

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

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