C#表达式求值算法(干货)
在讲述算法之前,我们需要先学习几个概念。
中缀表示法
中缀表示法就是我们人书写表达式的方法,如8/4+3*(6-2)。
后缀表示法
后缀表示法是从中缀表示法转化过来的,它满足以下条件:
(1)操作数的顺序与中缀表达式一致。
(2)没有括号。
(3)操作符没有优先级之分。
例如上面的表达式,其后缀形式是:84/362-*+
后缀表达式的特点对计算机计算非常有利。
二元运算符
需要两个操作数的运算符,例如是加法、减法、乘法、乘方、大于、等于等。
一元运算符
只需要一个操作数的运算符,例如是负数、逻辑非、正弦、平方根等。
我们下面介绍的算法,将支持以下运算:
数值运算 | 逻辑运算 | |
---|---|---|
二元 | + - * / %(求余) ^(乘方) | and or & | |
e(科学计数法) | < > = != <= >= | |
一元 | + - | ! |
sin cos tan asin acos atan | ||
sqrt lg |
利用这些运算,如果希望增加其他运算,会是很容易的。
一、表达式预处理
预处理包括去除空白字符、转成小写、判断括号是否匹配,以及加入结束符。
private static string PreTreat(string exp)
{if (string.IsNullOrEmpty(exp)){throw new Exception("表达式为空");}exp = exp.Replace(" ", "").Replace("\t", "").Replace("\r", "").Replace("\n", "");if (string.IsNullOrEmpty(exp)){throw new Exception("表达式为空");}if (!IsBracketMatch(exp)){throw new Exception("左右括号不匹配");}exp = exp.ToLower();exp = exp + "@";return exp;
}
结束符的作用是不需要每次扫描都判断是否越界。假如我们的表达式是8.1*sqrt(4)/-4+3*(16-23),经过预处理后,就变成了8.1*sqrt(4)/-4+3*(16-23)@。
二、元素切割
我们的表达式是一个字符串,由一个一个的字符组成,字符与字符之间并没有联系。我们需要把上面的字符串,变成以下的一个操作数和运算符分隔的列表:8.1,*,sqrt,(,4,),/,-,4,+,3,(,16,-,23,),@
切割的代码如下:
private static List<string> Segment(string exp)
{List<string> segments = new List<string>();int current = 0;int pre_type = 1;//上一个类型,1符号,2字母,3数字while (current < exp.Length){if (SPLIT_SYMBOL.Contains(exp[current])){segments.Add(exp[current].ToString());switch (exp[current]){case '<':switch (exp[current + 1]){case '>':case '=':segments[segments.Count - 1] += exp[current + 1];current++;break;}break;case '>':case '!':switch (exp[current + 1]){case '=':segments[segments.Count - 1] += exp[current + 1];current++;break;}break;}pre_type = 1;}else{int type = 3;if (LETTER.IsMatch(exp[current].ToString())){type = 2;}if (pre_type != type){segments.Add(exp[current].ToString());}else{segments[segments.Count - 1] += exp[current];}pre_type = type;}current++;}return segments;
}
三、转成后缀表示法
转换的方法如下:
(1)初始化一个堆栈和一个队列,堆栈存放中间过程,队列存放结果。(结果也可以用堆栈存放,不过要反过来装载一遍)
(2)从左到右读入中缀表达式,每次一个元素。
(3)如果元素是操作数,将它添加到结果队列。
(4)如果元素是个运算符,弹出运算符,直至遇见左括号、优先级较低的运算符。把这个运算符压入堆栈。
(5)如果元素是个左括号,把它压入堆栈。
(6)如果元素是个右括号,在遇见左括号前,弹出所有运算符,然后把它们添加到结果队列。
(7)如果到达输入字符串的末尾,弹出所有运算符并添加到结果队列。
代码如下:
private static Stack<object> Parse(List<string> segments)
{Stack<object> syntax = new Stack<object>();//语法单元堆栈Stack<object> operands = new Stack<object>();//操作数堆栈Stack<Operator> operators = new Stack<Operator>();//运算符堆栈bool pre_opt = true;//前一个符号是运算符for (int i = 0; i < segments.Count; i++){string ele = segments[i];if (ALL_OPERATOR.Contains(ele))//运算符{Operator opt = new Operator(ele, !pre_opt);pre_opt = true;//若当前运算符为结束运算符,则停止循环if (opt.Type == OperatorType.END){break;}//若当前运算符为左括号,则直接存入堆栈。if (opt.Type == OperatorType.LB){operators.Push(new Operator(OperatorType.LB));continue;}//若当前运算符为右括号,则依次弹出运算符堆栈中的运算符并存入到操作数堆栈,直到遇到左括号为止,此时抛弃该左括号.if (opt.Type == OperatorType.RB){while (operators.Count > 0){if (operators.Peek().Type != OperatorType.LB){operands.Push(operators.Pop());}else{operators.Pop();break;}}pre_opt = false;continue;}//若运算符堆栈为空,或者若运算符堆栈栈顶为左括号,则将当前运算符直接存入运算符堆栈.if (operators.Count == 0 || operators.Peek().Type == OperatorType.LB){operators.Push(opt);continue;}//若当前运算符优先级大于运算符栈顶的运算符,则将当前运算符直接存入运算符堆栈.if (opt.CompareTo(operators.Peek()) > 0){operators.Push(opt);}else{//若当前运算符若比运算符堆栈栈顶的运算符优先级低或相等,则输出栈顶运算符到操作数堆栈,直至运算符栈栈顶运算符低于(不包括等于)该运算符优先级,//或运算符栈栈顶运算符为左括号//并将当前运算符压入运算符堆栈。while (operators.Count > 0){if (opt.CompareTo(operators.Peek()) <= 0 && operators.Peek().Type != OperatorType.LB){operands.Push(operators.Pop());if (operators.Count == 0){operators.Push(opt);break;}}else{operators.Push(opt);break;}}}}else//操作数{operands.Push(ele);pre_opt = false;}}//转换完成,若运算符堆栈中尚有运算符时,//则依序取出运算符到操作数堆栈,直到运算符堆栈为空while (operators.Count > 0){operands.Push(operators.Pop());}//调整操作数栈中对象的顺序并输出到最终栈while (operands.Count > 0){syntax.Push(operands.Pop());}return syntax;
}
对于+、-符号的判断,我们采用以下方法:
(1)如果符号前是非右括号的运算符,则判定为正号、负号。
(2)除此,判定为加号、减号。
经过这一步骤后,上述的表达式转成如下的后缀表达式:
8.1,4,sqrt,*,4,-(负号),/,3,16,23,-,*,+
四、对后缀表达式进行计算
后缀表达式的计算还是比较简单的,方法如下:
(1)初始化一个空堆栈。
(2)从左到右读入后缀表达式。
(3)如果字符是一个操作数,把它压入堆栈。
(4)如果字符是个运算符,弹出两个操作数,执行恰当操作,然后把结果压入堆栈。
(5)到后缀表达式末尾,从堆栈中弹出结果。
实现代码:
private static string Evaluate(Stack<object> syntax)
{if (syntax.Count == 0){return null;}Stack<string> opds = new Stack<string>();Stack<object> pars = new Stack<object>();string opdA, opdB;double numA = 0, numB = 0;string strA = "", strB = "";foreach (object item in syntax){if (item is string){opds.Push((string)item);}else{switch (((Operator)item).Type){//二元case OperatorType.POW://乘方case OperatorType.SCI://科学记数法case OperatorType.FIX://定点case OperatorType.MUL://乘case OperatorType.DIV://除case OperatorType.MOD://余case OperatorType.ADD://加case OperatorType.SUB://减case OperatorType.LT://小于case OperatorType.GT://大于case OperatorType.LE://小于等于case OperatorType.GE://大于等于opdA = opds.Pop();opdB = opds.Pop();if (!double.TryParse(opdA.ToString(), out numA) || !double.TryParse(opdB.ToString(), out numB)){throw new Exception("操作数必须均为数字");}break;case OperatorType.AND://与case OperatorType.OR://或opdA = opds.Pop();opdB = opds.Pop();strA = opdA.ToString().ToLower();strB = opdB.ToString().ToLower();if (strA == "1" || strA == "true"){strA = "1";}else if (strA == "0" || strA == "false"){strA = "0";}else{throw new Exception("操作数必须均为逻辑类型");}if (strB == "1" || strB == "true"){strB = "1";}else if (strB == "0" || strB == "false"){strB = "0";}else{throw new Exception("操作数必须均为逻辑类型");}break;case OperatorType.ET://等于case OperatorType.UT://不等于opdA = opds.Pop();opdB = opds.Pop();strA = opdA.ToString().ToLower();strB = opdB.ToString().ToLower();if (strA == "1" || strA == "true"){strA = "1";}else if (strA == "0" || strA == "false"){strA = "0";}if (strB == "1" || strB == "true"){strB = "1";}else if (strB == "0" || strB == "false"){strB = "0";}break;//一元case OperatorType.PS://正case OperatorType.NS://负case OperatorType.TAN://正切case OperatorType.ATAN://反正切case OperatorType.SIN://正弦case OperatorType.ASIN://反正弦case OperatorType.COS://余弦case OperatorType.ACOS://反余弦case OperatorType.SQRT://开平方case OperatorType.LG://对数opdA = opds.Pop();if (!double.TryParse(opdA.ToString(), out numA)){throw new Exception("操作数必须为数字");}break;case OperatorType.NOT://非opdA = opds.Pop();strA = opdA.ToString().ToLower();if (strA == "1" || strA == "true"){strA = "1";}else if (strA == "0" || strA == "false"){strA = "0";}else{throw new Exception("操作数必须为逻辑类型");}break;}switch (((Operator)item).Type){//二元case OperatorType.POW://乘方opds.Push(Math.Pow(numB, numA).ToString());break;case OperatorType.SCI://科学记数法opds.Push((numB * Math.Pow(10, numA)).ToString());break;case OperatorType.FIX://定点opds.Push(numB.ToString("F" + numA));break;case OperatorType.MUL://乘opds.Push((numB * numA).ToString());break;case OperatorType.DIV://除opds.Push((numB / numA).ToString());break;case OperatorType.MOD://余opds.Push((numB % numA).ToString());break;case OperatorType.ADD://加opds.Push((numB + numA).ToString());break;case OperatorType.SUB://减opds.Push((numB - numA).ToString());break;case OperatorType.AND://与if (strA == "1" && strB == "1"){opds.Push("1");}else{opds.Push("0");}break;case OperatorType.OR://或if (strA == "1" || strB == "1"){opds.Push("1");}else{opds.Push("0");}break;case OperatorType.ET://等于if (strA == strB){opds.Push("1");}else{opds.Push("0");}break;case OperatorType.UT://不等于if (strA != strB){opds.Push("1");}else{opds.Push("0");}break;case OperatorType.LT://小于if (numB < numA){opds.Push("1");}else{opds.Push("0");}break;case OperatorType.GT://大于if (numB > numA){opds.Push("1");}else{opds.Push("0");}break;case OperatorType.LE://小于等于if (numB <= numA){opds.Push("1");}else{opds.Push("0");}break;case OperatorType.GE://大于等于if (numB >= numA){opds.Push("1");}else{opds.Push("0");}break;//一元case OperatorType.PS://正opds.Push(numA.ToString());break;case OperatorType.NS://负opds.Push((-numA).ToString());break;case OperatorType.TAN://正切opds.Push(Math.Tan(numA).ToString());break;case OperatorType.ATAN://反正切opds.Push(Math.Atan(numA).ToString());break;case OperatorType.SIN://正弦opds.Push(Math.Sin(numA).ToString());break;case OperatorType.ASIN://反正弦opds.Push(Math.Asin(numA).ToString());break;case OperatorType.COS://余弦opds.Push(Math.Cos(numA).ToString());break;case OperatorType.ACOS://反余弦opds.Push(Math.Acos(numA).ToString());break;case OperatorType.SQRT://开平方opds.Push(Math.Sqrt(numA).ToString());break;case OperatorType.LG://对数opds.Push(Math.Log10(numA).ToString());break;case OperatorType.NOT://非if (strA == "1"){opds.Push("0");}else{opds.Push("1");}break;}}}if (opds.Count == 1){return opds.Pop();}return null;
}
至此,表达式的求值完成。
C#表达式求值算法(干货)相关推荐
- 《Algorithms》—— Dijkstra 的双栈算术表达式求值算法
想当年学数据结构的时候,一直觉得这个是我一辈子都搞不懂的一个东西.现在看看...还挺简单的... 重点在于如何解析由括号.运算符和数字组成的字符串,并按照正确的顺序完成各种初级算术操作.利用了两个栈( ...
- 北京林业大学数据结构实验二 基于栈的算术表达式求值算法
第1关:基于栈的中缀算术表达式求值 参见课本P75 例3.3 #include <iostream> #include<iomanip>#define MAXSIZE 100 ...
- 编程题实训-实验2-基于栈的算术表达式求值算法(北京林业大学)
第1关:基于栈的中缀算术表达式求值 任务描述 本关任务:输入一个中缀算术表达式,求解表达式的值.运算符包括+.-.*./.(.).=,参加运算的数为double类型且为正数.(要求:直接针对中缀算术表 ...
- 表达式求值及转换算法
2019独角兽企业重金招聘Python工程师标准>>> 后缀表达式求值算法 stack operands; //运算数栈 while(没到表达式尾) {scanf("一个运 ...
- 数据结构—— 基于二叉树的算术表达式求值
实验五 基于二叉树的算术表达式求值 数据结构--中序表达式求值(栈实现) 实验目的: 1.掌握二叉树的二叉链表存储表示和二叉树的遍历等基本算法. 2.掌握根据中缀表达式创建表达式树的算法 3.掌握基于 ...
- 【数据结构】栈的应用-算术表达式求值#数据结构实验任务书
实验题目:栈的应用-算术表达式求值 正文 实验环境: Visual C++ 2010 实验目的: 1.掌握栈的定义及实现: 2.掌握利用栈求解算术表达式的方法. 实验内容: 通过修改完善教材中的算法3 ...
- 简单计算器(浙大复试题)表达式求值
读入一个只包含 +.-.*./ 的非负整数计算表达式,计算该表达式的值. 输入 测试输入包含若干测试用例,每个测试用例占一行,每行不超过200个字符,整数和运算符之间用一个空格分隔.没有非法表达式.当 ...
- 栈的应用-算数表达式求值
List item ** 实验要求** 河 南 师 范 大 学 20学年-21学年第 1 学期 数据结构实验任务书 专业名称: 实验学时: 4 课程名称:数据结构 任课教师: 王亚丽 实验题目:栈的应 ...
- 栈实现算术表达式求值
算术表达式求值 利用栈求解的一个典型的问题是算术表达式求值,例如:"3+4*2-(1+1)#",这样的表达式计算,在计算过程中,不是读到一个运算就立即计算,而是要与后面的运算符进行 ...
最新文章
- 34 多线程同步之Event
- C# DataTable转ListModel通用类
- C++实现线程安全的单例模式
- 【c基础】之 文件及其操作
- VMware8.0虚拟机中安装Ubuntu12.04使用NAT设置连接网络
- dj鲜生-35-设置django的session使用redis来存储
- Java 导出 Excel 文件
- Android程序的退出
- 计数器:counter
- 6Y叔的clusterProfiler-book阅读 Chapter 6 KEGG analysis
- android接口类命名规范_Android开发规范
- 阶段3 2.Spring_10.Spring中事务控制_10spring编程式事务控制2-了解
- uml通信图画法_UML各种图画法总结
- 移动端后台管理系统框架
- [嵌入式linux]RTL8111/RTL8168网卡内核驱动安装
- linux基础教程-黑马程序员汇总PDF
- 光线追踪技术 第二章
- 【高等数学】微积分----教你如何简单地推导求导公式(一)
- linux 命令详解 大于号_Linux 命令出现号(大于号)如何退出[组图]
- mysql:列类型之Spatial