ツリーの解析について質問があります:
文字列(数学表現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; // +, -, *, /
}
上記の表現文字列を表すツリーを構築するためにどのアルゴリズムを使用できますか?
Shunting-yard algorithm を使用します。ウィキペディアの説明は非常に包括的なものであり、十分であると思います。
また、 parsing-expression grammar などの正式な文法を記述して、ツールを使用してパーサーを生成することもできます。 PEGに関するこのサイト PEG解析用の3つのC/C++ライブラリをリストします。
最初のステップは、式の文法を書くことです。このような単純なケースの2番目のステップは、再帰降下パーサーを作成することです。これが推奨されるアルゴリズムです。これは、見栄えの良いC実装を備えた再帰降下パーサーに関するwikiページです。
#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 ))
この文法を使用して式を作成できます。
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, " `- ");
}
}
私はこのことをその日に処理するクラスを作成しました。これは少し冗長で、おそらく地球上で最も効率的なものではありませんが、符号付き/符号なし整数、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 から入手できます。