借助表达式树对四则运算表达式进行计算
Posted laggage
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了借助表达式树对四则运算表达式进行计算相关的知识,希望对你有一定的参考价值。
如何计算像这样的一个算术表达式:
-5+(-5)+35^3+14*(52+9)
学过数据结构的我们知道, 这是一个中缀表达式, 我们可以先把它转成前缀或者后缀表达式, 然后计算起来就比较简单了;
这里我使用后缀表达式来实现;
预备知识
- 数据结构 - 二叉树
- 设计模式 - 建造者 策略
- C#中的表达式树(
Expression
)
从后缀表达式生成表达式树
后缀表达式怎么生成表达式树?
我参考了 <<数据结构与算法分析-C语言描述>> 中给出的一个算法来实现将后缀表达式转化成C#中的 Expression
:
遍历后缀表达式字符串数组, 判断当前是操作符还是操作数, 如果是操作数, 则构造ConstantExpression
然后压栈, 如果是操作符, 则出两次栈, 构造一个BinaryExpression
然后将其压栈;
这里使用了建造者模式, 构建表达式树的函数放在了建造者类里面, 完整的代码地址在文末给出了;
/// <summary>
/// 从后缀表达式生成一个表达式树
/// </summary>
/// <param name="postFix">后缀表达式</param>
private void BuildExpressionFromPostFix(string[] postFix)
{
var stack = new Stack<Expression>();
BinaryExpression ex;
Expression l, r;
for (int i = 0;i < postFix.Length;i++)
{
if(IsArithmetricOperator(postFix[i]))
{
r = stack.Pop();
l = stack.Pop();
ex = Expression.MakeBinary(
ArthmetricOperatorsMapping[postFix[i][0]],
l, r);
stack.Push(ex);
}
else
{
stack.Push(
Expression.Constant(
double.Parse(postFix[i])));
}
}
if (stack.Peek() is BinaryExpression binaryExpression)
_exprTree = binaryExpression;
else if (stack.Peek() is ConstantExpression constantExpression)
_exprTree = Expression.Add(constantExpression, Expression.Constant(0d)); // 单独一个表达式 5
}
中缀表达式转成后缀表达式
这里输入的中缀表达式是一个字符串, 输出一个字符串数组保存后缀表达式
<<数据结构与算法分析-C语言描述>> 这本书在栈的那一章给出了中缀表达式转后缀表达式的算法, 简单来说就是:
遇到操作数, 操作数放到结果集里面去;
遇到运算符, 判断是否要压栈, 压栈的条件是
- 如果当前栈空, 则压栈
- 栈顶的操作符优先级低于当前操作符的优先级, 则压栈
出栈的条件: - 栈顶元素优先级高于或等于当前操作符的优先级, 则出栈直到栈顶元素优先级低于当前操作符, 然后将当前操作符入栈;
具体代码, 写的不好, 欢迎指正, 里面用到了建造者的成员变量, 完整代码看文末链接:
/// <summary>
/// 构建后缀表达式
/// </summary>
private void BuildPostFixExpr()
{
if (_postFixExpr == null)
{
var stack = new Stack<char>();
int sIndex = 0;
var list = new List<string>();
// 表达式开头可能出现 "-" 号
if (_expr.Length > 0 && IsArithmetricOperator(_expr[0]))
_expr = _expr.Insert(0, "0");
for (int i = 0; i< _expr.Length; i++)
{
if (IsArithmetricOperator(_expr[i]))
{
sIndex = i + 1;
if (stack.Count <= 0)
{
stack.Push(_expr[i]);
}
else
{
if (_expr[i] == ‘)‘)
{
while (stack.Peek() != ‘(‘)
{
list.Add(stack.Pop().ToString());
}
stack.Pop();
}
else if(i+1 < _expr.Length && _expr[i] == ‘(‘ &&
_expr[i+1] != ‘(‘ && IsArithmetricOperator(_expr[i+1]))
{
_expr = _expr.Insert(i+1, "0"); // 5-(-5) 的情况
stack.Push(_expr[i]);
}
else if (stack.Peek() != ‘(‘ && ArithmetricOperatorsPriority[stack.Peek()] >=
ArithmetricOperatorsPriority[_expr[i]])
{
while (stack.Count > 0 && stack.Peek() != ‘(‘ && ArithmetricOperatorsPriority[stack.Peek()] >=
ArithmetricOperatorsPriority[_expr[i]])
list.Add(stack.Pop().ToString());
stack.Push(_expr[i]);
}
else
stack.Push(_expr[i]);
}
}
else
{
if ((_expr.Length > i+1 && IsArithmetricOperator(_expr[i+1])) || i == _expr.Length - 1)
list.Add(_expr.Substring(sIndex, i-sIndex+1));
}
}
while (stack.Count > 0)
{
if (stack.Peek() == ‘(‘)
{
stack.Pop();
continue;
}
list.Add(stack.Pop().ToString());
}
_postFixExpr = list.ToArray();
}
}
获得了使用Expression
构成的表达式树, 如何计算最终结果呢
上面我们完成了基于Expression
的表达式树的构建, 需要注意的是, Expression
类是一个抽象类, 我们上面的构建的表达式树主要用到了他的两个派生类: ConstantExpression
和BinaryExpression
, 其中BinaryExpression
其实就是一个二叉树节点, 而ConstantExpression
呢, 顾名思义,就是一个保存了操作数的常量表达式;
所以最终如何计算得到表达式的结果呢?
有两种办法:
这里的话我使用了策略模式来封装这两种方案
策略接口:IExpressionTreeCalculatorEngine
using System.Linq.Expressions;
namespace SimpleCalculator
{
internal interface IExpressionTreeCalculatorEngine
{
double Calculate(Expression expression);
}
}
DefaultCalculatorEngine策略
这个策略比较简单, 直接将作为参数的传递过来的表达式通过 Expression.Lambda() 生成 lambda 表达式然后编译并调用得到最终结果;
using System;
using System.Linq.Expressions;
namespace SimpleCalculator
{
internal class DefaultCalculatorEngine : IExpressionTreeCalculatorEngine
{
public double Calculate(Expression expression)
{
Func<double> calculate = Expression.Lambda<Func<double>>(expression).Compile();
return calculate();
}
}
}
自己手动后序遍历递归求值(RecursionExpressionTreeCalculatorEngine)
using System;
using System.Diagnostics;
using System.Linq.Expressions;
namespace SimpleCalculator
{
internal sealed class RecursionExpressionTreeCalculatorEngine : ExpressionVisitor,IExpressionTreeCalculatorEngine
{
public double Calculate(Expression expression)
{
Expression exp = Visit(expression);
var constant = exp as ConstantExpression;
return (double)constant.Value;
}
protected override Expression VisitBinary(BinaryExpression node)
{
Expression l = Visit(node.Left);
Expression r = Visit(node.Right);
if (l is ConstantExpression cl && r is ConstantExpression cr)
switch (node.NodeType)
{
case ExpressionType.Add:
Debug.Write($"(+{cl.Value} {cr.Value})");
return Expression.Constant((double)cl.Value+(double)cr.Value);
case ExpressionType.Divide:
Debug.Write($"(/{cl.Value} {cr.Value})");
return Expression.Constant((double)cl.Value/(double)cr.Value);
case ExpressionType.Subtract:
Debug.Write($"(-{cl.Value} {cr.Value})");
return Expression.Constant((double)cl.Value-(double)cr.Value);
case ExpressionType.Multiply:
Debug.Write($"(*{cl.Value} {cr.Value})");
return Expression.Constant((double)cl.Value*(double)cr.Value);
case ExpressionType.PowerAssign:
case ExpressionType.Power:
Debug.Write($"(^{cl.Value} {cr.Value})");
return Expression.Constant(Math.Pow((double)cl.Value, (double)cr.Value));
case ExpressionType.Modulo:
Debug.Write($"(/{cl.Value} {cr.Value})");
return Expression.Constant((double)cl.Value%(double)cr.Value);
//case ExpressionType
default:
throw new NotSupportedException();
}
else
{
Debug.Write(node.ToString());
return node;
}
}
protected override Expression VisitConstant(ConstantExpression node)
{
return node;
}
}
}
这个类通过继承 ExpressionVisitor
来递归遍历各个表达式节点, 类似二叉树的后序遍历;
查看完整代码
以上是关于借助表达式树对四则运算表达式进行计算的主要内容,如果未能解决你的问题,请参考以下文章