返回顶部
首页 > 资讯 > 精选 >C#怎么利用后缀表达式解析计算字符串公式
  • 691
分享到

C#怎么利用后缀表达式解析计算字符串公式

2023-07-05 05:07:44 691人浏览 安东尼
摘要

本篇内容介绍了“C#怎么利用后缀表达式解析计算字符串公式”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!当我们拿到一个字符串比如:20+31*

本篇内容介绍了“C#怎么利用后缀表达式解析计算字符串公式”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

当我们拿到一个字符串比如:20+31*(100+1)的时候用口算就能算出结果为3151,因为这是中缀表达式对于人类的思维很简单,但是对于计算机就比较复杂了。相对的后缀表达式适合计算机进行计算。

我们就从简单到复杂,逐步实现对公式的解析(下述的代码没有经过严格验证,可能会存在极端情况的BUG,作为一种思路仅供参考,商用环境还需细细修改)。

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

我们从实现简单的数字的加减乘除开始主要是提供一个思路有需要可以自己修改扩展比如增加函数、字符串、数组等(推荐一个项目写的感觉就不错https://GitHub.com/KovtunV/NoStringEvaluating,那么我们只需要关注加减乘除等操作符、左右括号和操作数(整数、小数和负数),所以我们先建立三个枚举类BracketEnumnodeTypeEnumOperatorEnum如下:

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

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

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

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

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

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

然后我们需要做以下三步:

  • 解析公式将字符转化为便于操作的节点信息

  • 进行解析为后缀表达式

  • 进行计算

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

根据我们的NodeTypeEnum节点类型枚举我们需要三个不同的节点信息类方便我们的操作,我们先创建基类BaseNode以后的节点类都继承它

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;        }    }
public class NumberNode : BaseNode    {        /// <summary>        /// 数字值        /// </summary>        public double Number { get; }        public NumberNode(double number) : base(NodeTypeEnum.Number)        {            Number = number;        }    }
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;        }    }

有了节点信息类,那我们肯定还要有对应的解析类分别是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))            {                nodes.Add(new BracketNode(BracketEnum.Open));                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))            {                nodes.Add(new BracketNode(BracketEnum.Close));                return true;            }            return false;        }    }
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)            {                nodes.Add(new NumberNode(value));            }            return isTry;        }        /// <summary>        /// 判断是不是数字或者.        /// </summary>        /// <param name="ch">字符</param>        /// <returns></returns>        private static bool IsFloatingNumber(char ch)        {            //是不是十进制数            var isDigit = char.IsDigit(ch);            return isDigit || ch == '.';        }    }
/// <summary>    /// 操作符解读    /// </summary>    public static class OperatorReader    {        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()))            {                nodes.Add(new OperatorNode(GetOperatorKey(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            };        }    }

有了以上的准备,我们就可以将公式转为我们的节点信息了如下

/// <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++)            {                if (NumberReader.TryProceedNumber(nodes, formula.AsSpan(), ref index))                    continue;                if (OperatorReader.TryProceedOperator(nodes, formula.AsSpan(), ref index))                    continue;                if (BracketReader.TryProceedOpenBracket(nodes, formula.AsSpan(), ref index))                    continue;                if (BracketReader.TryProceedCloseBracket(nodes, formula.AsSpan(), ref index))                    continue;            }            return nodes;        }

2、转为后缀表达式

转为后缀表达式需要执行以下条件:

首先需要分配2个栈,一个作为临时存储运算符的栈S1(含一个结束符号),一个作为存放结果(逆波兰式)的栈S2(空栈),S1栈可先放入优先级最低的运算符#,注意,中缀式应以此最低优先级的运算符结束。可指定其他字符,不一定非#不可。从中缀式的左端开始取字符,逐序进行如下步骤:

(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)                {                    rpnNodes.Add(t);                    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)                        {                            rpnNodes.Add(tempNodes.Pop());                        }                        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                            {                                rpnNodes.Add(tempNodes.Pop());                            }                        }                        tempNodes.Pop();                        continue;                    }                }                //5、重复上述步骤            }            if(tempNodes.Count > 0)            {                rpnNodes.Add(tempNodes.Pop());            }            return rpnNodes;        }

3、计算后缀表达式

以(a+b)*c为例子进行说明:

(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位置)

经过以上运算,计算机就可以得到(a+b)*c的运算结果e了。

具体实现代码如下:

/// <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    }

最后串在一起就能得到结果啦,就像下面这样

/// <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;        }

“C#怎么利用后缀表达式解析计算字符串公式”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程网网站,小编将为大家输出更多高质量的实用文章!

--结束END--

本文标题: C#怎么利用后缀表达式解析计算字符串公式

本文链接: https://lsjlt.com/news/349756.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • C#利用后缀表达式解析计算字符串公式
    目录实现简单的数字的加减乘除1、解析公式转为节点信息2、转为后缀表达式3、计算后缀表达式当我们拿到一个字符串比如:20+31*(100+1)的时候用口算就能算出结果为3151,因为这...
    99+
    2023-02-23
    C#解析计算字符串公式 C#解析字符串公式 C#解析字符串 C# 后缀表达式
  • C#怎么利用后缀表达式解析计算字符串公式
    本篇内容介绍了“C#怎么利用后缀表达式解析计算字符串公式”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!当我们拿到一个字符串比如:20+31*...
    99+
    2023-07-05
  • java怎么计算字符串表达式的值
    在Java中,你可以使用ScriptEngine类来计算字符串表达式的值。下面是一个简单的例子: import javax.scri...
    99+
    2023-10-23
    java
  • C++中怎么将中缀表达式转换为后缀表达式
    本篇文章为大家展示了C++中怎么将中缀表达式转换为后缀表达式,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。一、思路:和中缀表达式的计算类似,只不过不用计算,把表达式输出即可用字符数组存储整行输入的中...
    99+
    2023-06-05
  • 怎么在python中利用后缀表达式实现一个计算器功能
    本文章向大家介绍怎么在python中利用后缀表达式实现一个计算器功能的基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。前缀表达式运算符在数字的前面1 + (2 + 3) * 4 - 5 (中缀)- + 1 * + ...
    99+
    2023-06-06
  • C#中怎么利用正则表达式匹配相关字符串
    今天就跟大家聊聊有关C#中怎么利用正则表达式匹配相关字符串,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。使用C#中使用正则表达式System.Text.RegularExpressi...
    99+
    2023-06-17
  • python利用正则表达式提取字符串
    前言 正则表达式的基础知识就不说了,有兴趣的可以点击这里,提取一般分两种情况,一种是提取在文本中提取单个位置的字符串,另一种是提取连续多个位置的字符串。日志分析会遇到这种情况,下面我会分别讲一下对应的方法。...
    99+
    2022-06-04
    字符串 正则表达式 python
  • Python中怎么利用正则表达式替换字符串
    Python中怎么利用正则表达式替换字符串,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。替换所有匹配的子串用newstring替换subject中所有与正则表达...
    99+
    2023-06-17
  • C#表达式和运算符详细解析
    目录类型转换1、表达式1.2 运算符分类2、数学运算符3、赋值运算符4、关系运算符5、布尔运算符6、位运算符6.1 &按位与运算6.2 或|按位运算6.3 异或^按位运算符6...
    99+
    2024-04-02
  • C#中怎么利用正则表达式定位字符
    今天就跟大家聊聊有关C#中怎么利用正则表达式定位字符,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。“定位字符”所代表的是一个虚的字符,它代表一个位置,你也可以直观地认为“定位字符”所...
    99+
    2023-06-17
  • C++ 如何使用栈求解中缀、后缀表达式的值
    目录1. 前言2. 中缀表达式2.1 求值流程2.2 演示表达式4*6^(3+3*3-2*3)-8 的求值过程当2.3 编码实现3.后缀表达式4. 中缀转后缀表达式4.1 流程演示4...
    99+
    2022-11-13
    C++中缀 后缀表达式的值 C++ 栈求解表达式的值
  • Python利用正则表达式从字符串提取数字
    目录前言利用正则表达式从字符串提取数字附python正则表达式抽取文本中的时间日期总结前言 正则表达式是一个特殊的字符序列,它能帮助你方便的检查一个字符串是否与某种模式匹配。 Pyt...
    99+
    2024-04-02
  • C#中怎么利用正则表达式重复描述字符
    C#中怎么利用正则表达式重复描述字符,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。“重复描述字符”是体现C#正则表达式“很好很强大”的地方之一:{n}  匹配前面...
    99+
    2023-06-17
  • C#字符串与正则表达式的图文详解
    1.创建字符串 string 变量名 = "字符串内容";            string str = "abcdrf";             Conso...
    99+
    2024-04-02
  • python怎么用正则表达式提取字符串
    今天小编给大家分享一下python怎么用正则表达式提取字符串的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。用正则表达式提取字...
    99+
    2023-07-05
  • 怎么解析正则表达式实现字符串的相关操作
    怎么解析正则表达式实现字符串的相关操作,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。正则表达式实现字符串的相关操作主要是实现对于字符串的开始和结束的锚定,那么具体的操作过程...
    99+
    2023-06-17
  • 怎么使用java正则表达式获取字符串
    要使用Java正则表达式获取字符串,可以按照以下步骤进行操作:1. 导入`java.util.regex`包。在Java中,正则表达...
    99+
    2023-09-05
    java
  • 怎么使用python正则表达式查找字符串
    使用Python的re模块来使用正则表达式查找字符串。首先,导入re模块:```pythonimport re```然后,定义一个正...
    99+
    2023-08-18
    python
  • java利用正则表达式如何实现查找字符串
    java利用正则表达式如何实现查找字符串?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。具体如下:Hello.java:package hello;import ...
    99+
    2023-05-31
    java 正则表达式 字符串
  • java如何实现OA信用盘系统制作字符串表达式计算
    这篇文章将为大家详细讲解有关java如何实现OA信用盘系统制作字符串表达式计算,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。方案一:eval 函数OA信用盘系统制作q<115.28.8.00.9.9...
    99+
    2023-06-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作