C#表达式求值算法(干货)

在讲述算法之前,我们需要先学习几个概念。

中缀表示法

中缀表示法就是我们人书写表达式的方法,如8/4+3*(6-2)。

后缀表示法

后缀表示法是从中缀表示法转化过来的,它满足以下条件:

(1)操作数的顺序与中缀表达式一致。

(2)没有括号。

(3)操作符没有优先级之分。

例如上面的表达式,其后缀形式是:84/362-*+

后缀表达式的特点对计算机计算非常有利。

二元运算符

需要两个操作数的运算符,例如是加法、减法、乘法、乘方、大于、等于等。

一元运算符

只需要一个操作数的运算符,例如是负数、逻辑非、正弦、平方根等。

 

我们下面介绍的算法,将支持以下运算:

  数值运算 逻辑运算
二元 + - * / %(求余) ^(乘方) and or & |
  e(科学计数法) < > = != <= >=
一元 + - !
  sin cos tan asin acos atan  
  sqrt lg  

利用这些运算,如果希望增加其他运算,会是很容易的。

一、表达式预处理

预处理包括去除空白字符、转成小写、判断括号是否匹配,以及加入结束符。

private static string PreTreat(string exp)
{
    if (string.IsNullOrEmpty(exp))
    {
        throw new Exception("表达式为空");
    }

    exp = exp.Replace(" ", "").Replace("\t", "").Replace("\r", "").Replace("\n", "");
    if (string.IsNullOrEmpty(exp))
    {
        throw new Exception("表达式为空");
    }

    if (!IsBracketMatch(exp))
    {
        throw new Exception("左右括号不匹配");
    }

    exp = exp.ToLower();
    exp = exp + "@";

    return exp;
}

结束符的作用是不需要每次扫描都判断是否越界。假如我们的表达式是8.1*sqrt(4)/-4+3*(16-23),经过预处理后,就变成了8.1*sqrt(4)/-4+3*(16-23)@。

二、元素切割

我们的表达式是一个字符串,由一个一个的字符组成,字符与字符之间并没有联系。我们需要把上面的字符串,变成以下的一个操作数和运算符分隔的列表:8.1,*,sqrt,(,4,),/,-,4,+,3,(,16,-,23,),@

切割的代码如下:

private static List Segment(string exp)
{
    List segments = new List();

    int current = 0;
    int pre_type = 1;//上一个类型,1符号,2字母,3数字
    while (current < exp.Length)
    {
        if (SPLIT_SYMBOL.Contains(exp[current]))
        {
            segments.Add(exp[current].ToString());

            switch (exp[current])
            {
                case '<':
                    switch (exp[current + 1])
                    {
                        case '>':
                        case '=':
                            segments[segments.Count - 1] += exp[current + 1];
                            current++;
                            break;
                    }
                    break;
                case '>':
                case '!':
                    switch (exp[current + 1])
                    {
                        case '=':
                            segments[segments.Count - 1] += exp[current + 1];
                            current++;
                            break;
                    }
                    break;
            }

            pre_type = 1;
        }
        else
        {
            int type = 3;
            if (LETTER.IsMatch(exp[current].ToString()))
            {
                type = 2;
            }

            if (pre_type != type)
            {
                segments.Add(exp[current].ToString());
            }
            else
            {
                segments[segments.Count - 1] += exp[current];
            }

            pre_type = type;
        }

        current++;
    }

    return segments;
}

三、转成后缀表示法

转换的方法如下:

(1)初始化一个堆栈和一个队列,堆栈存放中间过程,队列存放结果。(结果也可以用堆栈存放,不过要反过来装载一遍)
(2)从左到右读入中缀表达式,每次一个元素。
(3)如果元素是操作数,将它添加到结果队列。
(4)如果元素是个运算符,弹出运算符,直至遇见左括号、优先级较低的运算符。把这个运算符压入堆栈。
(5)如果元素是个左括号,把它压入堆栈。
(6)如果元素是个右括号,在遇见左括号前,弹出所有运算符,然后把它们添加到结果队列。
(7)如果到达输入字符串的末尾,弹出所有运算符并添加到结果队列。 

代码如下:

private static Stack Parse(List segments)
{
    Stack syntax = new Stack();//语法单元堆栈
    Stack operands = new Stack();//操作数堆栈
    Stack operators = new Stack();//运算符堆栈

    bool pre_opt = true;//前一个符号是运算符
    for (int i = 0; i < segments.Count; i++)
    {
        string ele = segments[i];
        if (ALL_OPERATOR.Contains(ele))//运算符
        {
            Operator opt = new Operator(ele, !pre_opt);
            pre_opt = true;

            //若当前运算符为结束运算符,则停止循环
            if (opt.Type == OperatorType.END)
            {
                break;
            }

            //若当前运算符为左括号,则直接存入堆栈。
            if (opt.Type == OperatorType.LB)
            {
                operators.Push(new Operator(OperatorType.LB));
                continue;
            }

            //若当前运算符为右括号,则依次弹出运算符堆栈中的运算符并存入到操作数堆栈,直到遇到左括号为止,此时抛弃该左括号.
            if (opt.Type == OperatorType.RB)
            {
                while (operators.Count > 0)
                {
                    if (operators.Peek().Type != OperatorType.LB)
                    {
                        operands.Push(operators.Pop());
                    }
                    else
                    {
                        operators.Pop();
                        break;
                    }
                }
                pre_opt = false;
                continue;
            }

            //若运算符堆栈为空,或者若运算符堆栈栈顶为左括号,则将当前运算符直接存入运算符堆栈.
            if (operators.Count == 0 || operators.Peek().Type == OperatorType.LB)
            {
                operators.Push(opt);
                continue;
            }
            //若当前运算符优先级大于运算符栈顶的运算符,则将当前运算符直接存入运算符堆栈.
            if (opt.CompareTo(operators.Peek()) > 0)
            {
                operators.Push(opt);
            }
            else
            {
                //若当前运算符若比运算符堆栈栈顶的运算符优先级低或相等,则输出栈顶运算符到操作数堆栈,直至运算符栈栈顶运算符低于(不包括等于)该运算符优先级,
                //或运算符栈栈顶运算符为左括号
                //并将当前运算符压入运算符堆栈。
                while (operators.Count > 0)
                {
                    if (opt.CompareTo(operators.Peek()) <= 0 && operators.Peek().Type != OperatorType.LB)
                    {
                        operands.Push(operators.Pop());
                        if (operators.Count == 0)
                        {
                            operators.Push(opt);
                            break;
                        }
                    }
                    else
                    {
                        operators.Push(opt);
                        break;
                    }
                }
            }
        }
        else//操作数
        {
            operands.Push(ele);
            pre_opt = false;
        }
    }

    //转换完成,若运算符堆栈中尚有运算符时,
    //则依序取出运算符到操作数堆栈,直到运算符堆栈为空
    while (operators.Count > 0)
    {
        operands.Push(operators.Pop());
    }

    //调整操作数栈中对象的顺序并输出到最终栈
    while (operands.Count > 0)
    {
        syntax.Push(operands.Pop());
    }

    return syntax;
} 
  

对于+、-符号的判断,我们采用以下方法:

(1)如果符号前是非右括号的运算符,则判定为正号、负号。

(2)除此,判定为加号、减号。

经过这一步骤后,上述的表达式转成如下的后缀表达式:

8.1,4,sqrt,*,4,-(负号),/,3,16,23,-,*,+

四、对后缀表达式进行计算

后缀表达式的计算还是比较简单的,方法如下:

(1)初始化一个空堆栈。
(2)从左到右读入后缀表达式。
(3)如果字符是一个操作数,把它压入堆栈。
(4)如果字符是个运算符,弹出两个操作数,执行恰当操作,然后把结果压入堆栈。
(5)到后缀表达式末尾,从堆栈中弹出结果。

实现代码:

private static string Evaluate(Stack syntax)
{
    if (syntax.Count == 0)
    {
        return null;
    }

    Stack opds = new Stack();
    Stack pars = new Stack();
    string opdA, opdB;
    double numA = 0, numB = 0;
    string strA = "", strB = "";
    foreach (object item in syntax)
    {
        if (item is string)
        {
            opds.Push((string)item);
        }
        else
        {
            switch (((Operator)item).Type)
            {
                //二元
                case OperatorType.POW://乘方
                case OperatorType.SCI://科学记数法
                case OperatorType.FIX://定点
                case OperatorType.MUL://乘
                case OperatorType.DIV://除
                case OperatorType.MOD://余
                case OperatorType.ADD://加
                case OperatorType.SUB://减

                case OperatorType.LT://小于
                case OperatorType.GT://大于
                case OperatorType.LE://小于等于
                case OperatorType.GE://大于等于
                    opdA = opds.Pop();
                    opdB = opds.Pop();
                    if (!double.TryParse(opdA.ToString(), out numA) || !double.TryParse(opdB.ToString(), out numB))
                    {
                        throw new Exception("操作数必须均为数字");
                    }
                    break;

                case OperatorType.AND://与
                case OperatorType.OR://或
                    opdA = opds.Pop();
                    opdB = opds.Pop();
                    strA = opdA.ToString().ToLower();
                    strB = opdB.ToString().ToLower();
                    if (strA == "1" || strA == "true")
                    {
                        strA = "1";
                    }
                    else if (strA == "0" || strA == "false")
                    {
                        strA = "0";
                    }
                    else
                    {
                        throw new Exception("操作数必须均为逻辑类型");
                    }
                    if (strB == "1" || strB == "true")
                    {
                        strB = "1";
                    }
                    else if (strB == "0" || strB == "false")
                    {
                        strB = "0";
                    }
                    else
                    {
                        throw new Exception("操作数必须均为逻辑类型");
                    }
                    break;

                case OperatorType.ET://等于
                case OperatorType.UT://不等于
                    opdA = opds.Pop();
                    opdB = opds.Pop();
                    strA = opdA.ToString().ToLower();
                    strB = opdB.ToString().ToLower();
                    if (strA == "1" || strA == "true")
                    {
                        strA = "1";
                    }
                    else if (strA == "0" || strA == "false")
                    {
                        strA = "0";
                    }
                    if (strB == "1" || strB == "true")
                    {
                        strB = "1";
                    }
                    else if (strB == "0" || strB == "false")
                    {
                        strB = "0";
                    }
                    break;

                //一元
                case OperatorType.PS://正
                case OperatorType.NS://负
                case OperatorType.TAN://正切
                case OperatorType.ATAN://反正切
                case OperatorType.SIN://正弦
                case OperatorType.ASIN://反正弦
                case OperatorType.COS://余弦
                case OperatorType.ACOS://反余弦
                case OperatorType.SQRT://开平方
                case OperatorType.LG://对数
                    opdA = opds.Pop();
                    if (!double.TryParse(opdA.ToString(), out numA))
                    {
                        throw new Exception("操作数必须为数字");
                    }
                    break;

                case OperatorType.NOT://非
                    opdA = opds.Pop();
                    strA = opdA.ToString().ToLower();
                    if (strA == "1" || strA == "true")
                    {
                        strA = "1";
                    }
                    else if (strA == "0" || strA == "false")
                    {
                        strA = "0";
                    }
                    else
                    {
                        throw new Exception("操作数必须为逻辑类型");
                    }
                    break;
            }


            switch (((Operator)item).Type)
            {
                //二元
                case OperatorType.POW://乘方
                    opds.Push(Math.Pow(numB, numA).ToString());
                    break;
                case OperatorType.SCI://科学记数法
                    opds.Push((numB * Math.Pow(10, numA)).ToString());
                    break;
                case OperatorType.FIX://定点
                    opds.Push(numB.ToString("F" + numA));
                    break;
                case OperatorType.MUL://乘
                    opds.Push((numB * numA).ToString());
                    break;
                case OperatorType.DIV://除
                    opds.Push((numB / numA).ToString());
                    break;
                case OperatorType.MOD://余
                    opds.Push((numB % numA).ToString());
                    break;
                case OperatorType.ADD://加
                    opds.Push((numB + numA).ToString());
                    break;
                case OperatorType.SUB://减
                    opds.Push((numB - numA).ToString());
                    break;
                case OperatorType.AND://与
                    if (strA == "1" && strB == "1")
                    {
                        opds.Push("1");
                    }
                    else
                    {
                        opds.Push("0");
                    }
                    break;
                case OperatorType.OR://或
                    if (strA == "1" || strB == "1")
                    {
                        opds.Push("1");
                    }
                    else
                    {
                        opds.Push("0");
                    }
                    break;
                case OperatorType.ET://等于
                    if (strA == strB)
                    {
                        opds.Push("1");
                    }
                    else
                    {
                        opds.Push("0");
                    }
                    break;
                case OperatorType.UT://不等于
                    if (strA != strB)
                    {
                        opds.Push("1");
                    }
                    else
                    {
                        opds.Push("0");
                    }
                    break;
                case OperatorType.LT://小于
                    if (numB < numA)
                    {
                        opds.Push("1");
                    }
                    else
                    {
                        opds.Push("0");
                    }
                    break;
                case OperatorType.GT://大于
                    if (numB > numA)
                    {
                        opds.Push("1");
                    }
                    else
                    {
                        opds.Push("0");
                    }
                    break;
                case OperatorType.LE://小于等于
                    if (numB <= numA)
                    {
                        opds.Push("1");
                    }
                    else
                    {
                        opds.Push("0");
                    }
                    break;
                case OperatorType.GE://大于等于
                    if (numB >= numA)
                    {
                        opds.Push("1");
                    }
                    else
                    {
                        opds.Push("0");
                    }
                    break;
                //一元
                case OperatorType.PS://正
                    opds.Push(numA.ToString());
                    break;
                case OperatorType.NS://负
                    opds.Push((-numA).ToString());
                    break;
                case OperatorType.TAN://正切
                    opds.Push(Math.Tan(numA).ToString());
                    break;
                case OperatorType.ATAN://反正切
                    opds.Push(Math.Atan(numA).ToString());
                    break;
                case OperatorType.SIN://正弦
                    opds.Push(Math.Sin(numA).ToString());
                    break;
                case OperatorType.ASIN://反正弦
                    opds.Push(Math.Asin(numA).ToString());
                    break;
                case OperatorType.COS://余弦
                    opds.Push(Math.Cos(numA).ToString());
                    break;
                case OperatorType.ACOS://反余弦
                    opds.Push(Math.Acos(numA).ToString());
                    break;
                case OperatorType.SQRT://开平方
                    opds.Push(Math.Sqrt(numA).ToString());
                    break;
                case OperatorType.LG://对数
                    opds.Push(Math.Log10(numA).ToString());
                    break;
                case OperatorType.NOT://非
                    if (strA == "1")
                    {
                        opds.Push("0");
                    }
                    else
                    {
                        opds.Push("1");
                    }
                    break;
            }
        }
    }
    if (opds.Count == 1)
    {
        return opds.Pop();
    }
    return null;
} 
  

至此,表达式的求值完成。

你可能感兴趣的:(算法,表达式求值,算法,堆栈,后缀表达式)