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<string> Segment(string exp)
{
    List<string> segments = new List<string>();

    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<object> Parse(List<string> segments)
{
    Stack<object> syntax = new Stack<object>();//语法单元堆栈
    Stack<object> operands = new Stack<object>();//操作数堆栈
    Stack<Operator> operators = new Stack<Operator>();//运算符堆栈

    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<object> syntax)
{
    if (syntax.Count == 0)
    {
        return null;
    }

    Stack<string> opds = new Stack<string>();
    Stack<object> pars = new Stack<object>();
    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;
}

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

  • 6
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
因各个项目中需要使用根据字符串计算数值,这里写出一个算法,专门计算字符串。配有大量常用公式。只有一个人方法,直接调用即可。 类名:CustomMath 函数名:Calculations(string value) 说明:求解算式表达式字符串的值 表达式中包含的符号或函数: truncate, ceiling,floor,round,log10, sign,sinh,sqrt, asin,atan,cosh, tanh, sin,cos,tan ,abs,acos, exp,log,max,min,pow,mod,+,-,*,/,',',(,) 函数说明:(不区分大小写) truncate(num) 计算指定数的整数部分 truncate(1.23)=1 ceiling (num) 返回大于或等于指定的双精度浮点数的最小整数值 ceiling(1.23)=2 floor(num) 返回小于或等于指定双精度浮点数的最大整数 floor(1.23)=1 round(num) 将双精度浮点值舍入为最接近的整数值 round(1.23)=1 round(num,num1) 将小数值按指定的小数位数舍入 round(1.23,1)=1.2 log10(num) 返回指定数字以 10 为底的对数 log10(10)=1 sign(num) 返回表示数字符号的值 sign(1.23)=1 sinh(num) 返回指定角度的双曲正弦值 sinh(1.23)=1.5644 sqrt(num) 返回指定数字的平方根 sqrt(9)=3 sqrt(num,num1) 返回指定数字的num1根 sqrt(27,3)=3 asin(num) 返回正弦值为指定数字的角度 asin(0.5)=PI/6 atan(num) 返回正切值为指定数字的角度 atan(1)=45 cosh(num) 返回指定角度的双曲余弦值 cosh(1.23)=1.8567 tanh(num) 返回指定角度的双曲正切值 tanh(1.23)=0.8425 sin(num) 返回指定角度的正弦值 sin(PI/6)=0.5 cos(num) 返回指定角度的余弦值 sin(PI/3)=0.5 tan(num) 返回指定角度的余切值 sin(PI/4)=1 abs(num) 返回数字的绝对值 abs(-12)=12 acos(num) 返回余弦值为指定数字的角度 acos(0.5)=PI/3 exp(num) 返回 e 的指定次幂 exp(1)=2.718 log(num) 返回指定数字的自然对数(底为 e) log(e)=1 log(num,num1) 返回指定数字在使用指定底时的对数 log(e,e)=1 max(num,um1) 返回最大值 max(1,2)=2 min(num,num1) 返回最小值 min(1,2)=1 pow(num,num1) 返回指定数字的指定次幂 pow(2,2)=4 mod(num,num1) 返回余数 mod(3,2)=1 常量: PI 值:3.14159265358979323846 E 值:2.7182818284590452354 YEAR 值:当前年份 MONTH 值:当前月份 DAY 值: 当前日 HOUR 值:当前时 MINUTE 值:当前分 SECOND 值:当前秒 RANDOM 值:一个随机数(0-1 之间) 实例 系统计算:1+2*3/4-0.5=2 函数计算:1+2*3/4-0.5=2 调用方式:CustomMath.Calculations("1+2*3/4-0.5") 系统计算:(1+2)*3/4-0.5=1.75 函数计算:(1+2)*3/4-0.5=1.75 调用方式:CustomMath.Calculations("(1+2)*3/4-0.5") 系统计算:(sin(pi)+sqrt(3+5*7+(2+8/4*5+2)))/6=1.20185042515466 公式计算:(sin(pi)+sqrt(3+5*7+(2+8/4*5+2)))/6=1.20185042515466 调用方式:CustomMath.Calculations("(sin(pi)+sqrt(3+5*7+(2+8/4*5+2)))/6") 系统计算:sin(pow(3,2)/4)+3.5-9*sqrt(81)=-76.7219268031121 函数计算:sin(pow(3,2)/4)+3.5-9*sqrt(81)=-76.7219268031121 调用方式:CustomMath.Calculations("sin(pow(3,2)/4)+3.5-9*sqrt(81)")
以下是使用C#编写麻雀搜索算法求解最大值的示例代码: ```csharp using System; class SparrowSearchAlgorithm { static void Main(string[] args) { int maxIter = 100; // 最大迭代次数 int popSize = 50; // 种群大小 double w = 0.5; // 惯性权重 double c1 = 2.0; // 自我认知学习因子 double c2 = 2.0; // 社会认知学习因子 double vmax = 0.5; // 最大速度 // 定义适应度函数,这里以求解函数f(x)=-x^2的最大值为例 Func<double, double> fitnessFunc = (x) => -x * x; // 初始化种群 double[] positions = new double[popSize]; double[] velocities = new double[popSize]; double[] pbestPositions = new double[popSize]; double[] pbestFitness = new double[popSize]; double gbestPosition = 0.0; double gbestFitness = double.MinValue; Random rand = new Random(); for (int i = 0; i < popSize; i++) { positions[i] = rand.NextDouble() * 10.0 - 5.0; velocities[i] = (rand.NextDouble() * 2.0 - 1.0) * vmax; pbestPositions[i] = positions[i]; pbestFitness[i] = fitnessFunc(positions[i]); if (pbestFitness[i] > gbestFitness) { gbestPosition = pbestPositions[i]; gbestFitness = pbestFitness[i]; } } // 开始迭代 for (int iter = 0; iter < maxIter; iter++) { for (int i = 0; i < popSize; i++) { // 鸟巢筛选 double fitness = fitnessFunc(positions[i]); if (fitness > pbestFitness[i]) { pbestFitness[i] = fitness; pbestPositions[i] = positions[i]; if (pbestFitness[i] > gbestFitness) { gbestPosition = pbestPositions[i]; gbestFitness = pbestFitness[i]; } } // 探索行为 double r1 = rand.NextDouble(); double r2 = rand.NextDouble(); velocities[i] = w * velocities[i] + c1 * r1 * (pbestPositions[i] - positions[i]) + c2 * r2 * (gbestPosition - positions[i]); // 限制速度 if (velocities[i] > vmax) { velocities[i] = vmax; } else if (velocities[i] < -vmax) { velocities[i] = -vmax; } // 移动麻雀位置 positions[i] += velocities[i]; // 局部搜索 double r3 = rand.NextDouble(); double r4 = rand.NextDouble(); if (r3 < 0.5) { positions[i] += r4 * (pbestPositions[i] - positions[i]); } else { positions[i] += r4 * (gbestPosition - positions[i]); } } } // 输出结果 Console.WriteLine("最优解为:{0}", gbestPosition); Console.WriteLine("最优值为:{0}", gbestFitness); } } ``` 需要注意的是,这里的适应度函数以求解函数f(x)=-x^2的最大值为例,实际应用中需要根据具体情况进行修改。另外,麻雀搜索算法的参数设置也需要根据具体问题进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值