在 C++ 中解析数学表达式
Posted
技术标签:
【中文标题】在 C++ 中解析数学表达式【英文标题】:parsing math expression in 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; // +, -, *, /
我可以使用什么算法来构建表示上述表达式字符串的树?
【问题讨论】:
Which Data Structure used to solve a simple math equation的可能重复 OP 问题的替代答案:***.com/a/32853177 【参考方案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)”。所以,我想对于字符串操作来说不算太破旧。 完成!感谢您的支持!以上是关于在 C++ 中解析数学表达式的主要内容,如果未能解决你的问题,请参考以下文章