最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C#利用后綴表達式解析計算字符串公式

 更新時間:2023年02月23日 08:30:03   作者:以往清泉  
當我們拿到一個字符串比如:20+31*(100+1)的時候用口算就能算出結果為3151,因為這是中綴表達式對于人類的思維很簡單,但是對于計算機就比較復雜了。相對的后綴表達式適合計算機進行計算。本文就來用后綴表達式實現解析計算字符串公式,需要的可以參考一下

當我們拿到一個字符串比如:20+31*(100+1)的時候用口算就能算出結果為3151,因為這是中綴表達式對于人類的思維很簡單,但是對于計算機就比較復雜了。相對的后綴表達式適合計算機進行計算。

我們就從簡單到復雜,逐步實現對公式的解析(下述的代碼沒有經過嚴格驗證,可能會存在極端情況的BUG,作為一種思路僅供參考,商用環(huán)境還需細細修改)。

實現簡單的數字的加減乘除

我們從實現簡單的數字的加減乘除開始主要是提供一個思路有需要可以自己修改擴展比如增加函數、字符串、數組等(推薦一個項目寫的感覺就不錯https://github.com/KovtunV/NoStringEvaluating,那么我們只需要關注加減乘除等操作符、左右括號和操作數(整數、小數和負數),所以我們先建立三個枚舉類BracketEnumNodeTypeEnumOperatorEnum如下:

BracketEnum是括號枚舉,也就是左右括號"()"

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

NodeTypeEnum是節(jié)點類型枚舉,就簡單分為操作符、操作數和括號

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

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

  • 解析公式將字符轉化為便于操作的節(jié)點信息
  • 進行解析為后綴表達式
  • 進行計算

1、解析公式轉為節(jié)點信息

根據我們的NodeTypeEnum節(jié)點類型枚舉我們需要三個不同的節(jié)點信息類方便我們的操作,我們先創(chuàng)建基類BaseNode以后的節(jié)點類都繼承它

public class BaseNode
    {
        public BaseNode(NodeTypeEnum nodeType)
        {
            NodeType = nodeType;
        }
        /// <summary>
        /// 節(jié)點類型
        /// </summary>
        public NodeTypeEnum NodeType { get; set; }
    }

然后我們分別創(chuàng)建BracketNode、NumberNodeOperatorNode類,分別是括號節(jié)點信息、操作數節(jié)點新和操作符節(jié)點信息,它們各有自己的具體實現,如下:

public class BracketNode : BaseNode
    {
        /// <summary>
        /// 括號值
        /// </summary>
        public BracketEnum Bracket { get; }
        /// <summary>
        /// 公式括號節(jié)點
        /// </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>
        /// 優(yōu)先級
        /// </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;
        }
    }

有了節(jié)點信息類,那我們肯定還要有對應的解析類分別是BracketReader(括號解析)NumberReader(操作數解析)OperatorReader(操作符解析),解析類就是為了將公式字符串解析為對應的節(jié)點信息具體如下:

public static class BracketReader
    {
        /// <summary>
        /// 左右括號字符
        /// </summary>
        private const char OPEN_BRACKET_CHAR = '(';
        private const char CLOSE_BRACKET_CHAR = ')';
        /// <summary>
        /// 嘗試獲取左括號
        /// </summary>
        /// <param name="nodes">公式節(jié)點信息</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">公式節(jié)點信息</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;
            //循環(huán)判斷數字
            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
            };
        }
    }

有了以上的準備,我們就可以將公式轉為我們的節(jié)點信息了如下

/// <summary>
        /// 解析公式為節(jié)點
        /// </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、轉為后綴表達式

轉為后綴表達式需要執(zhí)行以下條件:

首先需要分配2個棧,一個作為臨時存儲運算符的棧S1(含一個結束符號),一個作為存放結果(逆波蘭式)的棧S2(空棧),S1??上确湃雰?yōu)先級最低的運算符#,注意,中綴式應以此最低優(yōu)先級的運算符結束??芍付ㄆ渌址?,不一定非#不可。從中綴式的左端開始取字符,逐序進行如下步驟:

(1)若取出的字符是操作數,則分析出完整的運算數,該操作數直接送入S2棧。

(2)若取出的字符是運算符,則將該運算符與S1棧棧頂元素比較,如果該運算符(不包括括號運算符)優(yōu)先級高于S1棧棧頂運算符(包括左括號)優(yōu)先級,則將該運算符進S1棧,否則,將S1棧的棧頂運算符彈出,送入S2棧中,直至S1棧棧頂運算符(包括左括號)低于(不包括等于)該運算符優(yōu)先級時停止彈出運算符,最后將該運算符送入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、若取出的字符是運算符,則循環(huán)比較S1棧頂的運算符(包括左括號)優(yōu)先級,如果棧頂的運算符優(yōu)先級大于等于該運算符的優(yōu)先級,則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*按從左到右的順序壓入棧中,并且按照遇到運算符就把棧頂兩個元素出棧,執(zhí)行運算,得到的結果再入棧的原則來進行處理,那么ab+c*的執(zhí)行結果如下:

1)a入棧(0位置)

2)b入棧(1位置)

3)遇到運算符“+”,將a和b出棧,執(zhí)行a+b的操作,得到結果d=a+b,再將d入棧(0位置)

4)c入棧(1位置)

5)遇到運算符“*”,將d和c出棧,執(zhí)行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;
        }

數學操作符執(zhí)行代碼如下主要為了進行加減乘除簡單的計算:

/// <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、獲取公式節(jié)點
            var nodes = AnalysisFormulaToNodes(formula);
            //2、轉后綴表達式
            var rpnNodes = GetRPN(nodes);
            //3、計算對后綴表達式求值
            var result = CalculationRPN(rpnNodes);
            return result;
        }

到此這篇關于C#利用后綴表達式解析計算字符串公式的文章就介紹到這了,更多相關C#解析計算字符串公式內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • WPF實現html中的table控件的示例代碼

    WPF實現html中的table控件的示例代碼

    相信很多做WPF開發(fā)的小伙伴都遇到過表格類的需求,雖然現有的Grid控件也能實現,但是使用起來的體驗感并不好,所以本文我們就來用WPF自己實現一個html中的table控件吧
    2024-03-03
  • C#里SuperSocket庫不能發(fā)現命令的原因

    C#里SuperSocket庫不能發(fā)現命令的原因

    這篇文章主要介紹C#里SuperSocket庫不能發(fā)現命令的原因,在使用SuperSocket來寫服務器的過程中,這是一個非??焖俚拈_發(fā)方式,也非常好用。不過學習的曲線有點高,在使用的過程中經常會遇到各種各樣的問題。下面來看看學習舉例說明吧
    2021-10-10
  • C#中sleep和wait的區(qū)別分析

    C#中sleep和wait的區(qū)別分析

    這篇文章主要介紹了C#中sleep和wait的區(qū)別分析,有助于深入理解C#中線程的原理與使用技巧,非常具有實用價值,需要的朋友可以參考下
    2015-04-04
  • C#創(chuàng)建一個Word并打開的方法

    C#創(chuàng)建一個Word并打開的方法

    這篇文章主要介紹了C#創(chuàng)建一個Word并打開的方法,實例分析了C#操作word的常用技巧,非常具有實用價值,需要的朋友可以參考下
    2015-04-04
  • 詳解c# Emit技術

    詳解c# Emit技術

    這篇文章主要介紹了c# Emit技術的相關資料,幫助大家更好的理解和使用c#,感興趣的朋友可以了解下
    2020-12-12
  • C#算法之無重復字符的最長子串

    C#算法之無重復字符的最長子串

    這篇文章介紹了C#算法之無重復字符的最長子串,文中通過示例代碼介紹的非常詳細。對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-01-01
  • C# System.BadImageFormatException問題及解決

    C# System.BadImageFormatException問題及解決

    這篇文章主要介紹了C# System.BadImageFormatException問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • C#實現快捷鍵的幾種常用方法匯總

    C#實現快捷鍵的幾種常用方法匯總

    這篇文章主要介紹了C#實現快捷鍵的幾種常用方法,非常實用,需要的朋友可以參考下
    2014-08-08
  • C#實現軟件防破解和防調試的幾種有效措施

    C#實現軟件防破解和防調試的幾種有效措施

    軟件保護在現代應用程序開發(fā)中變得越來越重要,尤其是在面對軟件盜版、調試和破解等問題時,在C#開發(fā)中,雖然沒有完全防止破解的辦法,但通過采取一些有效的防護措施,可以顯著增加破解的難度并保護軟件的知識產權,本篇文章將探討在C#中實現軟件防破解和防調試的幾種常見技術
    2025-03-03
  • 關于C#中yield?return用法的思考

    關于C#中yield?return用法的思考

    在這篇文章中,我們將深入討論?C#?中yield?return的機制和用法,幫助您更好地理解這個強大的功能,并在實際開發(fā)中靈活使用它,感興趣的小伙伴可以跟隨小編一起學習一下
    2023-05-05

最新評論

名山县| 齐河县| 阿图什市| 肇庆市| 厦门市| 永年县| 南部县| 门头沟区| 崇阳县| 辽阳市| 将乐县| 堆龙德庆县| 罗山县| 马公市| 诏安县| 博爱县| 海南省| 蓬溪县| 贡觉县| 东兰县| 中江县| 鹤山市| 于都县| 磴口县| 哈巴河县| 谢通门县| 米脂县| 察雅县| 濉溪县| 伊春市| 香港| 玛纳斯县| 清河县| 咸阳市| 汾阳市| 永兴县| 离岛区| 平和县| 托克逊县| 且末县| 和顺县|