• 支持函数,变量的算术表达式计算(二、中缀转后缀)


    中缀转后缀需要处理的有:
    1. 操作数,操作符的提取
    2. 括号等关系到运算符优先级的符号
    3. 一元操作符(如 +(正), -(负)) 等
    4. 操作符和操作数的匹配,括号的匹配,(函数参数的个数是否正确等)

    基本思路如下:
    用一个链表 List<ExpressionToken> 储存将要生成的后缀表达式
    用一个栈 Stack<OperatorType> 储存操作符
    判断当前节点, 如果是操作数, 直接加入后缀表达式中, 如果是操作符,则比较前一个操作符和当前操作符的优先级,
    如果前一个操作符优先级较高,则将前一个操作符加入后缀表达式中,否则将操作符压入操作符栈,如果遇到反括号 ')', 则在操作符栈中反向搜索,直到遇到匹配的正括号为止,将中间的操作符依次加到后缀表达式中。
    举例:  15+25*2, 共有 15, +, 25, *, 2 五个符号,下面一步步列出每一步操作
    第一步: 数字加入后缀表达式
     操作符栈  空
     后缀表达式  15
    第二步:+ 号 压入操作符栈
     操作符栈  +
     后缀表达式  15
    第三步:
     操作符栈  +
     后缀表达式  15 25
    第四步:是一个*号,因为*号的优先级比+号高,所以直接压入操作符栈中
     操作符栈  + *
     后缀表达式  15 25
    第五步:
     操作符栈  + *
     后缀表达式  15 25 2
    第六步:没有符号了,直接将操作符栈中剩余的依次加到表达式中,最终的结果
     操作符栈  空
     后缀表达式  15 25 2 * +

    那假如括号呢?例如 (15 + 25) * 2, 共有7个符号,每一步的操作为
    第一步: 左括号直接压入操作符栈
     操作符栈  (
     后缀表达式  空
    第二步:数字加入表达式中
     操作符栈  (
     后缀表达式  15
    第三步:+号压入操作符栈
     操作符栈  ( +
     后缀表达式  15
    第四步:数字加入表达式中
     操作符栈  ( +
     后缀表达式  15 25
    第五步:遇到反括号了,将两个括号之间操作符依次加入到表达式中,并删除匹配的正括号
     操作符栈  空
     后缀表达式  15 25 +
    第六步:*号压入操作符栈
     操作符栈  *
     后缀表达式  15 25 +
    第七步:数字加入表达式中
     操作符栈  *
     后缀表达式  15 25 + 2
    第八步:没有符号了,直接将操作符栈中剩余的依次加到表达式中,最终的结果
     操作符栈  空
     后缀表达式  15 25 + 2 *
    这样就可以看出优先级对表达式的影响。

    依照上述要求, 编写相应的函数:
    首先定义几个变量
            bool needOp = false;    //此时需要操作符(如果不是,则说明操作符丢失)
            bool needParentheses = false;//前一个操作符是函数,所以下一个操作符必须是左括号
            bool needDigit = false//前一个操作符是 -(负号),所以下一个操作符必须是数字

    1. 操作数的提取
       操作数的提取只需要注意一点,就是正负号的问题(+/-),它们不但可以当作二元操作符,还能当一元操作符, 比如
       15 + -9 ,此时的-号就应该是一元操作符
            /// <summary>
            
    /// 操作数
            
    /// </summary>
            
    /// <param name="index">表达式当前字符的索引</param>
            
    /// <param name="expression">当前表达式</param>

            public void ProcDigit(ref int index, ref string expression)
            
    {
                
    if (needOp)
                    
    throw new ExpressionException("缺少操作符"1001);

                StringBuilder str 
    = new StringBuilder();
                
    for (int i = index; i < expression.Length; i++)
                
    {
                    
    char c = expression[i];
                    
    if (char.IsDigit(c) || c == '.')
                        str.Append(c);
                    
    else
                    
    {
                        
    break;
                    }

                    index 
    = i;
                }

                needOp 
    = true;
                
    decimal data = Convert.ToDecimal(str.ToString());
                
    //如果前一个是一元操作符(-) 负号,则须对数值进行处理
                if (needDigit)
                
    {
                    
    if (stackOp.Pop() == OperatorType.Subtract)
                        data 
    = -data;
                    needDigit 
    = false;
                }

                lstExp.Add(
    new ExpressionToken(TokenType.Numeric
                    , data));
            }


    2. 操作符的提取
            /// <summary>
            
    /// 提取操作符
            
    /// </summary>
            
    /// <param name="index"></param>
            
    /// <param name="op"></param>

            public void ProcOperator(int index, OperatorType op)
            
    {
                
    if (!needOp)
                    
    throw new ExpressionException("缺少操作数"1002);
                
    if (stackOp.Count > 0)
                
    {
                    
    while (stackOp.Count > 0)
                    
    {
                        OperatorType opPrev 
    = stackOp.Peek();
                        
    if (!IsBaseOperator(opPrev) || PRI(opPrev, op) < 0)
                            
    break;
                        
    //如果前一个操作符的优先级比当前操作符高,则需将前一个操作符
                        
    //加入到表达式中,并从操作符栈中清除
                        lstExp.Add(new ExpressionToken(TokenType.Operator, opPrev));
                        stackOp.Pop();
                    }

                    stackOp.Push(op);
                }

                
    else
                
    {
                    stackOp.Push(op);
                }

                needOp 
    = false;
            }

    3. 括号的处理
     括号的处理分正括号和反括号两种, 正括号处理比较简单,直接压入操作符栈,反括号就比较麻烦,需要考虑多个因素,代码如下
            /// <summary>
            
    /// 括号的处理
            
    /// </summary>
            
    /// <param name="index"></param>
            
    /// <param name="op"></param>

            public void ProcParentheses(int index, OperatorType op)
            
    {
                
    if (op == OperatorType.L_Parentheses) //左括号
                {
                    needOp 
    = false;
                    needParentheses 
    = false;
                    stackOp.Push(op);
                }

                
    else if (op == OperatorType.R_Parentheses) //右括号
                {
                    
    bool find = false;
                    
    //如果是函数,则此处为函数的参数个数
                    
    //通过统计逗号的个数得出参数的个数
                    int funcParam = 1;  
                    
    while (stackOp.Count > 0)
                    
    {
                        
    if (stackOp.Peek() != OperatorType.L_Parentheses)
                        
    {
                            OperatorType opPop 
    = stackOp.Pop();
    //此处代码用于判断函数参数个数,本节可忽略
                            if (opPop == OperatorType.Comma)
                            
    {
                                funcParam
    ++;
                            }

                            
    else
                                lstExp.Add(
    new ExpressionToken(TokenType.Operator, opPop));
                        }

                        
    else
                        
    {
                            stackOp.Pop();
                            find 
    = true;
                            
    break;
                        }

                    }

                    
    if (!find)
                        
    throw new ExpressionException("括号不匹配.(缺少左括号)"1003);
                }

            }


    4.比较两个函数的优先级,比较简单

            /// <summary>
            
    /// 比较两个操作符的优先级
            
    /// </summary>

            public int PRI(OperatorType op1, OperatorType op2)
            
    {
                
    return (int)op1 - (int)op2;
            }

    5. 逐字节解析算术表达式,然后分别调用上述几个函数,具体分析见注释
            /// <summary>
            
    /// 将算术表达式转换为逆波兰表达式
            
    /// </summary>

            public void ConvertExpression()
            
    {
                
    for
                
    if (!needOp)
                
    {
                    
    //最后一个操作符必须是数字或变量
                    throw new ExpressionException("缺少操作数"1002);
                }

                
    while (stackOp.Count > 0)
                
    {
                    
    if (stackOp.Peek() == OperatorType.L_Parentheses)
                        
    throw new ExpressionException("括号不匹配(缺少右括号)"1004);
                    lstExp.Add(
    new ExpressionToken(TokenType.Operator, stackOp.Pop()));
                }

            }

    OK, 中缀转后缀完成了, 再加上前一节的计算后缀表达式,终于可以写一个简单的计算器了。


    点击此处下载

    待续。。。。。。。
  • 相关阅读:
    用php爬取网页
    无论我是一只菜鸟笨鸟
    有线网卡与无线网卡同时使用
    scapy 命令理解
    Wireshark Filter
    python OS/pdb 模块及数据类型基础
    scapy down and install
    python 字符操作函数
    python 类型集
    python 科学计算
  • 原文地址:https://www.cnblogs.com/michaelhuwei/p/1023105.html
Copyright © 2020-2023  润新知