# C#后缀表达式解析计算字符串公式

### 实现简单的数字的加减乘除

BracketEnum是括号枚举，也就是左右括号"()"

public enum BracketEnum
{
/// <summary>
/// Undefined
/// </summary>
Undefined = 0,
/// <summary>
/// 左括号
/// </summary>
Open,
/// <summary>
/// 右括号
/// </summary>
Close
}
View Code

NodeTypeEnum是节点类型枚举，就简单分为操作符、操作数和括号

public enum NodeTypeEnum
{
/// <summary>
/// Null
/// </summary>
Null = 0,
/// <summary>
/// 操作数
/// </summary>
Number,
/// <summary>
/// 操作符
/// </summary>
Operator,
/// <summary>
/// 括号
/// </summary>
Bracket,
}
View Code

OperatorEnum是操作符枚举，主要就是加减乘除这些简单的

public enum OperatorEnum
{
/// <summary>
/// Undefined
/// </summary>
Undefined = 0,
/// <summary>
/// +
/// </summary>
Plus,
/// <summary>
/// -
/// </summary>
Minus,
/// <summary>
/// *
/// </summary>
Multiply,
/// <summary>
/// /
/// </summary>
Divide,
/// <summary>
/// ^
/// </summary>
Power,
}
View Code

1. 解析公式将字符转化为便于操作的节点信息
2. 进行解析为后缀表达式
3. 进行计算

1、解析公式转为节点信息

public class BaseNode
{
public BaseNode(NodeTypeEnum nodeType)
{
NodeType = nodeType;
}
/// <summary>
/// 节点类型
/// </summary>
public NodeTypeEnum NodeType { get; set; }
}

然后我们分别创建BracketNodeNumberNodeOperatorNode类，分别是括号节点信息、操作数节点新和操作符节点信息，它们各有自己的具体实现，如下：

public class BracketNode : BaseNode
{
/// <summary>
/// 括号值
/// </summary>
public BracketEnum Bracket { get; }
/// <summary>
/// 公式括号节点
/// </summary>
public BracketNode(BracketEnum bracket) : base(NodeTypeEnum.Bracket)
{
Bracket = bracket;
}
}
View Code
public class NumberNode : BaseNode
{
/// <summary>
/// 数字值
/// </summary>
public double Number { get; }
public NumberNode(double number) : base(NodeTypeEnum.Number)
{
Number = number;
}
}
View Code
public class OperatorNode : BaseNode
{
/// <summary>
/// 操作字符串枚举
/// </summary>
public OperatorEnum OperatorKey { get; }
/// <summary>
/// 优先级
/// </summary>
public int Priority { get; }
public OperatorNode(OperatorEnum operatorKey) : base(NodeTypeEnum.Operator)
{
OperatorKey = operatorKey;
Priority = GetPriority();
}
private int GetPriority()
{
var priority = OperatorKey switch
{
OperatorEnum.Power => 6,
OperatorEnum.Multiply => 5,
OperatorEnum.Divide => 5,
OperatorEnum.Plus => 4,
OperatorEnum.Minus => 4,
_ => 0
};
return priority;
}
}
View Code

有了节点信息类，那我们肯定还要有对应的解析类分别是BracketReader(括号解析)NumberReader(操作数解析)OperatorReader(操作符解析)，解析类就是为了将公式字符串解析为对应的节点信息具体如下：

public static class BracketReader
{
/// <summary>
/// 左右括号字符
/// </summary>
private const char OPEN_BRACKET_CHAR = '(';
private const char CLOSE_BRACKET_CHAR = ')';
/// <summary>
/// 尝试获取左括号
/// </summary>
/// <param name="nodes">公式节点信息</param>
/// <param name="formula">公式字符</param>
/// <param name="index">公式读取的下标</param>
/// <returns></returns>
public static bool TryProceedOpenBracket(List<BaseNode> nodes, ReadOnlySpan<char> formula, ref int index)
{
if (formula[index].Equals(OPEN_BRACKET_CHAR))
{
return true;
}
return false;
}
/// <summary>
/// 尝试获取右括号
/// </summary>
/// <param name="nodes">公式节点信息</param>
/// <param name="formula">公式字符</param>
/// <param name="index">公式读取的下标</param>
/// <returns></returns>
public static bool TryProceedCloseBracket(List<BaseNode> nodes, ReadOnlySpan<char> formula, ref int index)
{
if (formula[index].Equals(CLOSE_BRACKET_CHAR))
{
return true;
}
return false;
}
}
View Code
    public static class NumberReader
{
/// <summary>
/// 尝试读取数字
/// </summary>
public static bool TryProceedNumber(List<BaseNode> nodes, ReadOnlySpan<char> formula, ref int index)
{
double value = 0;
var isTry = false;//是否转换成功
var isNegative = formula[index] == '-';//是否是负数
var localIndex = isNegative ? index + 1 : index;
//循环判断数字
for (int i = localIndex; i < formula.Length; i++)
{
var ch = formula[i];
var isLastChar = i + 1 == formula.Length;

if (IsFloatingNumber(ch))
{
//如果最后一个并且成功
if (isLastChar && double.TryParse(formula.Slice(index, formula.Length - index), out value))
{
index = i;
isTry = true;
break;
}
}
else if(double.TryParse(formula.Slice(index, i - index), out value))
{
//如果不是数字比如是字母，则直接判断之前的数字
index = i - 1;
isTry = true;
break;
}
else
{
break;
}
}
if (isTry)
{
}
return isTry;
}
/// <summary>
/// 判断是不是数字或者.
/// </summary>
/// <param name="ch">字符</param>
/// <returns></returns>
private static bool IsFloatingNumber(char ch)
{
//是不是十进制数
var isDigit = char.IsDigit(ch);
return isDigit || ch == '.';
}
}
View Code
    /// <summary>
/// 操作符解读
/// </summary>
{
private static readonly string[] _operators = new[] { "+", "-", "*", "/", "^" };

/// <summary>
/// 尝试获取操作符
/// </summary>
public static bool TryProceedOperator(List<BaseNode> nodes, ReadOnlySpan<char> formula, ref int index)
{
if (_operators.Contains(formula[index].ToString()))
{
return true;
}
return false;
}
/// <summary>
/// 获取对应枚举
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
private static OperatorEnum GetOperatorKey(string name)
{
return name switch
{
"+" => OperatorEnum.Plus,
"-" => OperatorEnum.Minus,
"*" => OperatorEnum.Multiply,
"/" => OperatorEnum.Divide,
"^" => OperatorEnum.Power,

_ => OperatorEnum.Undefined
};
}
}
View Code

        /// <summary>
/// 解析公式为节点
/// </summary>
/// <param name="formula">公式字符串</param>
/// <returns></returns>
public static List<BaseNode> AnalysisFormulaToNodes(string formula)
{
var nodes = new List<BaseNode>();
for(var index = 0;index< formula.Length; index++)
{
continue;
continue;
continue;
continue;
}
return nodes;
}

2、转为后缀表达式

（1）若取出的字符是操作数，则分析出完整的运算数，该操作数直接送入S2栈。
（2）若取出的字符是运算符，则将该运算符与S1栈栈顶元素比较，如果该运算符(不包括括号运算符)优先级高于S1栈栈顶运算符（包括左括号）优先级，则将该运算符进S1栈，否则，将S1栈的栈顶运算符弹出，送入S2栈中，直至S1栈栈顶运算符（包括左括号）低于（不包括等于）该运算符优先级时停止弹出运算符，最后将该运算符送入S1栈。
（3）若取出的字符是“（”，则直接送入S1栈顶。
（4）若取出的字符是“）”，则将距离S1栈栈顶最近的“（”之间的运算符，逐个出栈，依次送入S2栈，此时抛弃“（”。
（5）重复上面的1~4步，直至处理完所有的输入字符。
（6）若取出的字符是“#”，则将S1栈内所有运算符（不包括“#”），逐个出栈，依次送入S2栈。

        /// <summary>
/// 转为后缀表达式
/// </summary>
/// <param name="nodes"></param>
/// <returns></returns>
public static List<BaseNode> GetRPN(List<BaseNode> nodes)
{
var rpnNodes = new List<BaseNode>();
var tempNodes = new Stack<BaseNode>();
foreach(var t in nodes)
{
//1、如果是操作数直接入栈
if(t.NodeType == NodeTypeEnum.Number)
{
continue;
}
//2、若取出的字符是运算符,则循环比较S1栈顶的运算符（包括左括号）优先级，如果栈顶的运算符优先级大于等于该运算符的优先级，则S1栈顶运算符弹出加入到S2中直至不满足条件为止，最后将该运算符送入S1中。
if (t.NodeType == NodeTypeEnum.Operator)
{
while (tempNodes.Count > 0)
{
var peekOperatorNode = tempNodes.Peek() as OperatorNode;
if (peekOperatorNode != null && peekOperatorNode.Priority >= (t as OperatorNode).Priority)
{
}
else
{
break;
}

}
tempNodes.Push(t);
continue;
}
//3、若取出的字符是“（”，则直接送入S1栈顶
if(t.NodeType == NodeTypeEnum.Bracket)
{
if((t as BracketNode).Bracket == BracketEnum.Open)
{
tempNodes.Push(t);
continue;
}
}
//4、若取出的字符是“）”，则将距离S1栈栈顶最近的“（”之间的运算符，逐个出栈，依次送入S2栈，此时抛弃“（”。
if (t.NodeType == NodeTypeEnum.Bracket)
{
if ((t as BracketNode).Bracket == BracketEnum.Close)
{
while (tempNodes.Count > 0)
{
var peekBracketNode = tempNodes.Peek() as BracketNode;
if (tempNodes.Peek().NodeType == NodeTypeEnum.Bracket && peekBracketNode != null && peekBracketNode.Bracket == BracketEnum.Open)
{
break;
}
else
{
}
}
tempNodes.Pop();
continue;
}
}
//5、重复上述步骤
}
if(tempNodes.Count > 0)
{
}
return rpnNodes;
}
View Code

3、计算后缀表达式

(a+b)*c的逆波兰式为ab+c*，假设计算机把ab+c*按从左到右的顺序压入栈中，并且按照遇到运算符就把栈顶两个元素出栈，执行运算，得到的结果再入栈的原则来进行处理，那么ab+c*的执行结果如下：
1）a入栈（0位置）
2）b入栈（1位置）
3）遇到运算符“+”，将a和b出栈，执行a+b的操作，得到结果d=a+b，再将d入栈（0位置）
4）c入栈（1位置）
5）遇到运算符“*”，将d和c出栈，执行d*c的操作，得到结果e，再将e入栈（0位置）

        /// <summary>
/// 计算后缀表达式
/// </summary>
/// <param name="nodes"></param>
/// <returns></returns>
public static double CalculationRPN(List<BaseNode> nodes)
{
double result = 0;
Stack<BaseNode> stack = new Stack<BaseNode>();
foreach(var t in nodes)
{
if(t.NodeType == NodeTypeEnum.Number)
{
//操作数直接入栈
stack.Push(t);
}
else if(t.NodeType == NodeTypeEnum.Operator)
{
//操作符弹出栈顶两个进行计算
var a = stack.Pop();
var b = stack.Pop();
var operate = t as OperatorNode;
var value = operate.OperatorKey switch
{
// 数学操作符
OperatorEnum.Multiply => OperatorService.Multiply(a, b),
OperatorEnum.Divide => OperatorService.Divide(a, b),
OperatorEnum.Plus => OperatorService.Plus(a, b),
OperatorEnum.Minus => OperatorService.Minus(a, b),
OperatorEnum.Power => OperatorService.Power(a, b),
};

stack.Push(new NumberNode(value));
}
}
result = (stack.Pop() as NumberNode).Number;
return result;
}

    /// <summary>
/// 操作符服务
/// </summary>
public static class OperatorService
{
#region Math

public static double Multiply(in BaseNode a, in BaseNode b)
{
var (result, _a, _b) = IsNumber(a, b);
if (result)
{
return _a * _b;
}
return default;
}

public static double Divide(in BaseNode a, in BaseNode b)
{
var (result, _a, _b) = IsNumber(a, b);
if (result)
{
return _a / _b;
}
return default;
}

public static double Plus(in BaseNode a, in BaseNode b)
{
var (result, _a, _b) = IsNumber(a, b);
if (result)
{
return _a + _b;
}
return default;
}

public static double Minus(in BaseNode a, in BaseNode b)
{
var (result, _a, _b) = IsNumber(a, b);
if (result)
{
return _a - _b;
}
return default;
}

public static double Power(in BaseNode a, in BaseNode b)
{
var (result, _a, _b) = IsNumber(a, b);
if (result)
{
return Math.Pow(_a, _b);
}
return default;
}
/// <summary>
/// 判断是不是数字类型，并返回数字
/// </summary>
/// <param name="a"></param>
/// <returns></returns>
private static (bool,double,double) IsNumber(BaseNode a, in BaseNode b)
{
if(a.NodeType == NodeTypeEnum.Number && b.NodeType == NodeTypeEnum.Number)
{
var _a = a as NumberNode;
var _b = b as NumberNode;
return (true, _a.Number, _b.Number);
}
return (false, default, default);
}
#endregion
}
View Code

        /// <summary>
/// 计算
/// </summary>
/// <param name="formula">公式字符串</param>
/// <returns></returns>
public static double Calculation(string formula)
{
//1、获取公式节点
var nodes = AnalysisFormulaToNodes(formula);
//2、转后缀表达式
var rpnNodes = GetRPN(nodes);
//3、计算对后缀表达式求值
var result = CalculationRPN(rpnNodes);
return result;
}

posted @ 2023-02-22 23:17  以往清泉  阅读(475)  评论(2编辑  收藏  举报
//替换成自己路径的js文件