web-dev-qa-db-ja.com

C ++での数式の解析

ツリーの解析について質問があります:

文字列(数学表現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; // +, -, *, /
}

上記の表現文字列を表すツリーを構築するためにどのアルゴリズムを使用できますか?

24
Hal Nuevemil

Shunting-yard algorithm を使用します。ウィキペディアの説明は非常に包括的なものであり、十分であると思います。

また、 parsing-expression grammar などの正式な文法を記述して、ツールを使用してパーサーを生成することもできます。 PEGに関するこのサイト PEG解析用の3つのC/C++ライブラリをリストします。

10
Kos

_(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 は再帰的降下解析を詳細に説明し、実装できます。

5

最初のステップは、式の文法を書くことです。このような単純なケースの2番目のステップは、再帰降下パーサーを作成することです。これが推奨されるアルゴリズムです。これは、見栄えの良いC実装を備えた再帰降下パーサーに関するwikiページです。

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

4
jahhaj
#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 ))
3
BLUEPIXY

この文法を使用して式を作成できます。

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, " `- ");
    }
}
3
jxh

私はこのことをその日に処理するクラスを作成しました。これは少し冗長で、おそらく地球上で最も効率的なものではありませんが、符号付き/符号なし整数、double、float、論理演算、ビット演算を処理します。

数値のオーバーフローとアンダーフローを検出し、構文に関する説明テキストとエラーコードを返し、倍精度整数を処理するか、またはサイネージを無視するように強制することができ、ユーザーが定義可能な精度とスマートな丸めをサポートし、DebugMode(true)を設定するとその動作を表示します。

最後に......外部ライブラリに依存しないため、ドロップインするだけです。

サンプル使用法:

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 から入手できます。

1
NTDLS