你是否遇到这样的需求:如何计算“1.0+3/2-tan(45)/(1+1)+Abs(-10)-floor(2.55)” 这一串字符串的值?
思路:
定义:
逆波兰表达式又叫做后缀表达式。在通常的表达式中,二元运算符总是置于与之相关的两个运算对象之间,这种表示法也称为中缀表示。波兰逻辑学家J.Lukasiewicz于1929年提出了另一种表示表达式的方法,按此方法,每一运算符都置于其运算对象之后,故称为后缀表示。
人类最熟悉的一种表达式1+2,(1+2)*3,3+42+4等都是中缀表示法。对于人们来说,也是最直观的一种求值方式,先算括号里的,然后算乘除,最后算加减,但是,计算机处理中缀表达式却并不方便。
然后我们还需明确一些概念,下面通过我们最熟悉的中缀表达式画出一棵语法树来直观认识一下前后缀表达式的生成。以A+B*(C-D)-E*F
为例:
从上面可以发现,其实问题就2个:
1. 将人类常用的中缀表达式转换为后缀表达式
2. 计算后缀表达式。
转换为后缀表达式
这里我们要用到堆栈Stack, 详细概念可以点这里。
1.数字直接入栈
2.运算符要与栈顶元素比较
①栈为空直接入栈
②运算符优先级大于栈顶元素优先级则直接入栈
③小于或等于则出栈入列,再与栈顶元素进行比较,直到运算符优先级小于栈顶元
素优先级后,操作符再入栈
3.操作符是 (
则无条件入栈
4.操作符为 )
,则依次出栈入列,直到匹配到第一个(
为止,此操作符直接舍弃,(
直接出栈舍弃
代码实现如下,首先定义操作数类型,因为我们不仅想计算简单的加减乘除,还想实现一些简单的函数和逻辑运算。
/// <summary>
/// 操作数类型
/// </summary>
public enum Type
{
/// <summary>
/// 函数
/// </summary>
FUNC = 1,
/// <summary>
/// 日期
/// </summary>
DATE = 2,
/// <summary>
/// 数字
/// </summary>
NUMBER = 3,
/// <summary>
/// 布尔
/// TODO
/// </summary>
BOOLEAN = 4,
/// <summary>
/// 字符串
/// </summary>
STRING = 5
}
然后我们定义一个操作数类:
/// <summary>
/// 操作数类
/// </summary>
public class Operand
{
public Operand(Type type, object value)
{
this.Type = type;
this.Value = value;
}
public Operand(string opd, object value)
{
this.Type = ConvertOperand(opd);
this.Value = value;
}
/// <summary>
/// 操作数类型
/// </summary>
public Type Type { get; set; }
/// <summary>
/// 关键字
/// </summary>
public string Key { get; set; }
/// <summary>
/// 操作数值
/// </summary>
public object Value { get; set; }
/// <summary>
/// 转换操作数到指定的类型
/// </summary>
/// <param name="opd">操作数</param>
/// <returns>返回对应的操作数类型</returns>
public static Type ConvertOperand(string opd)
{
if (opd.IndexOf("(") > -1)
{
return Type.FUNC;
}
else if (IsNumber(opd))
{
return Type.NUMBER;
}
else if (IsDate(opd))
{
return Type.DATE;
}
else
{
return Type.STRING;
}
}
/// <summary>
/// 判断对象是否为数字
/// </summary>
/// <param name="value">对象值</param>
/// <returns>是返回真,否返回假</returns>
public static bool IsNumber(object value)
{
return Regex.IsMatch(value.ToString(), @"^[+-]?\d*[.]?\d*$");
// return double.TryParse(value.ToString(), out _); 用正则表达式快一点
}
/// <summary>
/// 判断对象是否为日期
/// </summary>
/// <param name="value">对象值</param>
/// <returns>是返回真,否返回假</returns>
public static bool IsDate(object value)
{
return DateTime.TryParse(value.ToString(), out _);
}
}
然后定义运算符的枚举,从小到大优先级递减:
/// <summary>
/// 运算符类型(从上到下优先级依次递减),数值越大,优先级越低
/// </summary>
public enum OptType
{
#region 最高优先级操作符
/// <summary>
/// 左括号:(,left bracket
/// </summary>
LB = 1,
/// <summary>
/// 右括号),right bracket
/// </summary>
RB = 2,
/// <summary>
/// 逻辑非,!,NOT
/// </summary>
NOT = 3,
/// <summary>
/// 正号,+,positive sign
/// </summary>
PS = 5,
/// <summary>
/// 负号,-,negative sign
/// </summary>
NS = 7,
#endregion
#region 函数
/// <summary>
/// 正切,tan
/// </summary>
TAN = 10,
/// <summary>
/// 正切,tan
/// </summary>
COT = 11,
/// <summary>
/// 反正切,atan
/// </summary>
ATAN = 12,
/// <summary>
/// 正弦
/// </summary>
SIN = 13,
/// <summary>
/// 余弦
/// </summary>
COS = 14,
/// <summary>
/// 绝对值
/// </summary>
Abs = 15,
/// <summary>
/// 开平方
/// </summary>
Sqrt = 16,
/// <summary>
/// 向下取整
/// </summary>
Floor = 17,
/// <summary>
/// 向上取整
/// </summary>
Ceiling = 18,
/// <summary>
/// 四舍五入2位小数
/// </summary>
Round = 19,
/// <summary>
/// 平方
/// </summary>
Pow = 20,
/// <summary>
/// 立方
/// </summary>
Cube = 21,
/// <summary>
/// 自然对数
/// </summary>
Ln = 22,
/// <summary>
/// 10为底的对数
/// </summary>
Log = 23,
#endregion
#region 其他操作符
/// <summary>
/// 乘,*,multiplication
/// </summary>
MUL = 130,
/// <summary>
/// 除,/,division
/// </summary>
DIV = 131,
/// <summary>
/// 余,%,modulus
/// </summary>
MOD = 132,
/// <summary>
/// 加,+,Addition
/// </summary>
ADD = 140,
/// <summary>
/// 减,-,subtraction
/// </summary>
SUB = 141,
/// <summary>
/// 小于,less than
/// </summary>
LT = 150,
/// <summary>
/// 小于或等于,less than or equal to
/// </summary>
LE = 151,
/// <summary>
/// 大于,>,greater than
/// </summary>
GT = 152,
/// <summary>
/// 大于或等于,>=,greater than or equal to
/// </summary>
GE = 153,
/// <summary>
/// 等于,=,equal to
/// </summary>
ET = 160,
/// <summary>
/// 不等于,unequal to
/// </summary>
UT = 161,
/// <summary>
/// 逻辑与,&,AND
/// </summary>
AND = 170,
/// <summary>
/// 逻辑或,|,OR
/// </summary>
OR = 171,
/// <summary>
/// 逗号,comma
/// </summary>
CA = 180,
/// <summary>
/// 结束符号 @
/// </summary>
END = 255,
/// <summary>
/// 错误符号
/// </summary>
ERR = 256
#endregion
}
再定义一个运算符类,用来把输入字符串中的运算符提取出来:
/// <summary>
/// 运算符类
/// </summary>
public class Operator
{
public Operator(OptType type, string value)
{
this.Type = type;
this.Value = value;
}
/// <summary>
/// 运算符类型
/// </summary>
public OptType Type { get; set; }
/// <summary>
/// 运算符值
/// </summary>
public string Value { get; set; }
/// <summary>
/// 对于>或者<运算符,判断实际是否为>=,<>、<=,并调整当前运算符位置
/// </summary>
/// <param name="curr