12 + sin(sqr(9) + 9) + abs(-90)
给这么一段字符串给你, 要如何才能正确计算出它的值? (注: 值应为 103 )
算术表达式的计算有几种方法,本文只讨论“后缀表达式(也叫'逆波兰表达式')方法”
后缀表达式是啥意思呢? 顾名思义,就是操作符在操作数的后面,比如 12+36 转换为后缀表达式后就变成
12 36 +
注:本文中的后缀表达式是用 List 存储,当然用 Stack 也可以(可能用栈会更好)
后缀表达式可以将复杂的算术表达式变得很简单,它的计算逻辑为
1.遍历整个后缀表达式
2.如果后缀表达式当前节点是数字,则跳过,继续往下遍历
3.如果后缀表达式当前节点是操作符,则将前两个节点取出,用当前操作符作运算
计算完后,将结果存入当前结点, 并删除前两个操作数节点
重复上面三个过程,直到节点数为 1 (此节点的数据即为最终数据)
我们先来模拟一下 12+36*9 这个表达式的计算
先转换为后缀表达式 12 36 9 * + (转换方法见后续文章)
1. 遍历表达式直到遇见操作符 * (第4个节点)
2. 取出前两个操作数(36 和 9) 和 * 作运算,结果存入当前节点
执行完此步骤后,后缀表达式为
12 324 +
3. 再重复1,2两步骤,即可得到最终结果 336 , 是不是很简单呢?
下面我们来构造一个可计算后缀表达式的类
先定义节点类型枚举
/// 节点类型
/// </summary>
public enum TokenType
{
/// <summary>
/// 操作数
/// </summary>
Numeric,
/// <summary>
/// 操作符
/// </summary>
Operator
}
操作符枚举
{
Plus, //"+",
Subtract,// "-",
MultiPly,// "*",
Divide, //"/",
//后面可继续添加函数等
}
节点类
/// 节点类
/// </summary>
public class ExpressionToken
{
private TokenType type;
/// <summary>
/// 节点类型
/// </summary>
public TokenType Type
{
get { return type; }
set { type = value; }
}
private object data;
/// <summary>
/// 节点数据
/// </summary>
public object Data
{
get { return data; }
set { data = value; }
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="type">节点类型</param>
/// <param name="data">节点数据</param>
public ExpressionToken(TokenType type, object data)
{
this.type = type;
this.data = data;
}
}
表达式计算类
{
/// <summary>
/// 表达式节点列表
/// </summary>
List<ExpressionToken> lstExp = new List<ExpressionToken>();
string strExpression;
public Expression(string exp)
{
if (exp == null)
throw new ArgumentNullException();
strExpression = exp;
}
/// <summary>
/// 开始计算
/// </summary>
/// <returns></returns>
private object CalcInner(List<ExpressionToken> exp)
{
int index = 0;
//储存数据
List<decimal?> digit = new List<decimal?>();
while (index < exp.Count)
{
if (exp.Count == 1 && exp[0].Type == TokenType.Numeric)
break;
ExpressionToken token = exp[index];
switch (token.Type)
{
//如果是数字,则将值存入 digit 中
case TokenType.Numeric:
digit.Add(Convert.ToDecimal(exp[index].Data));
index++;
break;
case TokenType.Operator:
//二元表达式,需要二个参数, 如果是函数的话,可能需要更多的参数
int paramCount = 2;
//计算操作数的值
if (digit.Count < paramCount)
{
throw new ExpressionException("缺少操作数");
}
//传入参数
decimal?[] data = new decimal?[paramCount];
for (int i = 0; i < paramCount; i++)
{
data[i] = digit[index - paramCount + i];
}
//将计算结果再存入当前节点
exp[index].Data = CalcOperator((OperatorType)token.Data, data);
exp[index].Type = TokenType.Numeric;
//将操作数节点删除
for (int i = 0; i < paramCount; i++)
{
exp.RemoveAt(index - i - 1);
digit.RemoveAt(index - i - 1);
}
index -= paramCount;
break;
default:
break;
}
}
if (exp.Count == 1)
{
switch (exp[0].Type)
{
case TokenType.Numeric:
return exp[0].Data;
default:
throw new ExpressionException("缺少操作数", 1002);
}
}
else
{
throw new ExpressionException("缺少操作符或操作数", 1002);
}
}
public object Calc()
{
return CalcInner(lstExp);
}
/// <summary>
/// 计算表达式的值
/// (因为不确定参数有几个,所以用数组传进来)
/// 比如,函数可能只需要1个参数,也可能需要3个参数
/// </summary>
public object CalcOperator(OperatorType op, decimal?[] data)
{
//暂只编号写基本四则运算的代码,无函数计算
decimal? d1 = data[0];
decimal? d2 = data[1];
if (d1 == null || d2 == null)
return DBNull.Value;
switch (op)
{
case OperatorType.Plus:
return d1 + d2;
case OperatorType.Subtract:
return d1 - d2;
case OperatorType.MultiPly:
return d1 * d2;
case OperatorType.Divide:
if (d2 == 0)
throw new DivideByZeroException();
return d1 / d2;
}
return 0;
}
}
里面的一些基本函数不作赘述。
该函数使用很简单,只有个Calc方法
Expression exp = new Expression("12+36*9");
object result = exp.Calc()); //当然,现在还没转换后缀表达式功能,所以无法计算 :)
...........待续