【问题标题】:parsing math expression in c++在 C++ 中解析数学表达式
【发布时间】:2012-07-28 17:14:11
【问题描述】:

我有一个关于解析树的问题:

我有一个字符串(数学表达式 estring),例如:(a+b)*c-(d-e)*f/g。我必须在树中解析该表达式:

class Exp{};
class Term: public Exp{
    int n_;
}

class Node: Public Exp{
    Exp* loperator_;
    Exp* roperator_;
    char operation; // +, -, *, /
}

我可以使用什么算法来构建表示上述表达式字符串的树?

【问题讨论】:

标签: c++ parsing tree


【解决方案1】:

使用Shunting-yard algorithm。维基百科的描述很全面,我希望它就足够了。

您也可以尝试编写一个正式的语法,例如parsing-expression grammar,并使用工具生成解析器。 This site about PEGs 列出了 3 个用于 PEG 解析的 C/C++ 库。

【讨论】:

  • 以前从未见过调车场,这是避免RDP递归的巧妙方法。
【解决方案2】:

(a+b)*c-(d-e)*f/g 是一个固定表达式。

要轻松创建tree,请先将其转换为前缀表达式。

从示例中, (A * B) + (C / D) 的前缀是 + (* A B) (/ C D)

     (+)            
     / \        
    /   \       
  (*)    (/)         
  / \   /  \        
 A   B C    D   

 ((A*B)+(C/D))  

然后你的树看起来有 + 作为它的根节点。您可以继续填充左右子树,关于每个运算符。

另外,this link 详细解释了递归下降解析,并且可以实现。

【讨论】:

  • 即时翻译步骤有什么帮助?
  • 我真的以为 OP 正在寻找这个。他可以轻松地从 IMO 前缀表达式构建他的树。
  • 他必须解析中缀来构建前缀,所以这根本没有帮助。
【解决方案3】:
#include <algorithm>
#include <iostream>
#include <string>
#include <cctype>
#include <iterator>

using namespace std;

class Exp{
public:
//  Exp(){}
    virtual void print(){}
    virtual void release(){}
};
class Term: public Exp {
    string val;
public:
    Term(string v):val(v){}
    void print(){
        cout << ' ' << val << ' ';
    }
    void release(){}
};

class Node: public Exp{
    Exp *l_exp;
    Exp *r_exp;
    char op; // +, -, *, /
public:
    Node(char op, Exp* left, Exp* right):op(op),l_exp(left), r_exp(right){}
    ~Node(){
    }
    void print(){
        cout << '(' << op << ' ';
        l_exp->print();
        r_exp->print();
        cout  << ')';
    }
    void release(){
        l_exp->release();
        r_exp->release();
        delete l_exp;
        delete r_exp;
    }
};

Exp* strToExp(string &str){
    int level = 0;//inside parentheses check
    //case + or -
    //most right '+' or '-' (but not inside '()') search and split
    for(int i=str.size()-1;i>=0;--i){
        char c = str[i];
        if(c == ')'){
            ++level;
            continue;
        }
        if(c == '('){
            --level;
            continue;
        }
        if(level>0) continue;
        if((c == '+' || c == '-') && i!=0 ){//if i==0 then s[0] is sign
            string left(str.substr(0,i));
            string right(str.substr(i+1));
            return new Node(c, strToExp(left), strToExp(right));
        }
    }
    //case * or /
    //most right '*' or '/' (but not inside '()') search and split
    for(int i=str.size()-1;i>=0;--i){
        char c = str[i];
        if(c == ')'){
            ++level;
            continue;
        }
        if(c == '('){
            --level;
            continue;
        }
        if(level>0) continue;
        if(c == '*' || c == '/'){
            string left(str.substr(0,i));
            string right(str.substr(i+1));
            return new Node(c, strToExp(left), strToExp(right));
        }
    }
    if(str[0]=='('){
    //case ()
    //pull out inside and to strToExp
        for(int i=0;i<str.size();++i){
            if(str[i]=='('){
                ++level;
                continue;
            }
            if(str[i]==')'){
                --level;
                if(level==0){
                    string exp(str.substr(1, i-1));
                    return strToExp(exp);
                }
                continue;
            }
        }
    } else
    //case value
        return new Term(str);
cerr << "Error:never execute point" << endl;
    return NULL;//never
}

int main(){
    string exp(" ( a + b ) * c - ( d - e ) * f / g");
    //remove space character
    exp.erase(remove_if(exp.begin(), exp.end(), ::isspace), exp.end());
    Exp *tree = strToExp(exp);
    tree->print();
    tree->release();
    delete tree;
}
//output:(- (* (+  a  b ) c )(/ (* (-  d  e ) f ) g ))

【讨论】:

  • 还要注意前缀树不需要括号
  • strToExp 最好使用另一个函数,可以转包。
  • @MooingDuck "prefix tree": 只不过是打印的一种表示而已。
  • 您需要的不仅仅是这个描述吗?
  • 我不明白你的第一条评论。我知道前缀树只是一种表示,它仍然不需要括号。而且我相信你的代码有一个“2+5*7”的错误(但我没有测试过,可能是错的)
【解决方案4】:

第一步是为您的表达式编写语法。这种简单情况的第二步是编写递归下降解析器,这是我推荐的算法。这是关于递归下降解析器的 wiki 页面,它具有漂亮的 C 实现。

http://en.wikipedia.org/wiki/Recursive_descent_parser

【讨论】:

    【解决方案5】:

    您可以使用此语法来创建您的表达式。

    exp:
        /* empty */
      | non_empty_exp { print_exp(); }
      ;
    non_empty_exp:
        mult_div_exp
      | add_sub_exp
      ;
    mult_div_exp:
        primary_exp
      | mult_div_exp '*' primary_exp { push_node('*'); }
      | mult_div_exp '/' primary_exp { push_node('/'); }
      ;
    add_sub_exp:
        non_empty_exp '+' mult_div_exp { push_node('+'); }
      | non_empty_exp '-' mult_div_exp { push_node('-'); }
      ;
    primary_exp:
      | '(' non_empty_exp ')'
      | NUMBER { push_term($1); }
      ;
    

    以下是您的词法分析器。

    [ \t]+   {}
    [0-9]+   { yylval.number = atoi(yytext); return NUMBER; }
    [()]     { return *yytext; }
    [*/+-]   { return *yytext; }
    

    使用这些例程,随心所欲地构建表达式:

    std::list<Exp *> exps;
    
    /* push a term onto expression stack */
    void push_term (int n) {
        Term *t = new Term;
        t->n_ = n;
        exps.push_front(t);
    }
    
    /* push a node onto expression stack, top two in stack are its children */
    void push_node (char op) {
        Node *n = new Node;
        n->operation_ = op;
        n->roperator_ = exps.front();
        exps.pop_front();
        n->loperator_ = exps.front();
        exps.pop_front();
        exps.push_front(n);
    }
    
    /*
     * there is only one expression left on the stack, the one that was parsed
     */
    void print_exp () {
        Exp *e = exps.front();
        exps.pop_front();
        print_exp(e);
        delete e;
    }
    

    以下例程可以漂亮地打印您的表达式树:

    static void
    print_exp (Exp *e, std::string ws = "", std::string prefix = "") {
        Term *t = dynamic_cast<Term *>(e);
        if (t) { std::cout << ws << prefix << t->n_ << std::endl; }
        else {
            Node *n = dynamic_cast<Node *>(e);
            std::cout << ws << prefix << "'" << n->operation_ << "'" << std::endl;
            if (prefix.size()) {
                ws += (prefix[1] == '|' ? " |" : "  ");
                ws += "  ";
            }
            print_exp(n->loperator_, ws, " |- ");
            print_exp(n->roperator_, ws, " `- ");
        }
    }
    

    【讨论】:

      【解决方案6】:

      我以前写了一个类来处理这个问题。它有点冗长,也许不是地球上最有效的东西,但它处理有符号/无符号整数、双精度、浮点、逻辑和按位运算。

      它检测数字上溢和下溢,返回有关语法的描述性文本和错误代码,可以强制将双精度数作为整数处理,或忽略符号,支持用户可定义的精度和智能舍入,如果您设置 DebugMode(真的)。

      最后......它不依赖任何外部库,所以你可以把它放进去。

      示例用法:

      CMathParser parser;
      
      double dResult = 0;
      int iResult = 0;
      
      //Double math:
      if (parser.Calculate("10 * 10 + (6 ^ 7) * (3.14)", &dResult) != CMathParser::ResultOk)
      {
          printf("Error in Formula: [%s].\n", parser.LastError()->Text);
      }
      printf("Double: %.4f\n", dResult);
      
      //Logical math:
      if (parser.Calculate("10 * 10 > 10 * 11", &iResult) != CMathParser::ResultOk)
      {
          printf("Error in Formula: [%s].\n", parser.LastError()->Text);
      }
      printf("Logical: %d\n", iResult);
      

      最新版本始终可通过CMathParser GitHub Repository 获得。

      【讨论】:

      • 显然该类将在 2.8Ghz CPU 上以每秒大约 500,000 个表达式处理表达式“10*10*(10*10)”。所以,我想对于字符串操作来说不算太破旧。
      • 完成!感谢您的支持!
      猜你喜欢
      • 1970-01-01
      • 2011-02-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多