微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

表达式计算器的实现

一、前言

    关于表达式计算器的实现,在这里分享一下我的思路,也希望大家提出一些改进建议。

 

二、实现表达式计算的主要思路。

    1、使用的数据结构。

     以前的版本实现表达式计算用的是二叉树数据结构,二叉树有两个子节点,最多支持双目运算符或者带两个参数的函数,可是如果函数的参数很多,就不好处理了,所以当前的版本,用的数据结构是动态数组,实现原理就是先把字符串表达式转换成动态数组,数组中存储运算符、参与运算的数、括号等。这样运算符或者函数的参数个数就不受限制,可以支持更多类型的运算符。

 

    2、对运算符进行分类

    对运算符的分类处理是该程序的一个重要思路,运算符虽然有很多,但是可以将运算符归类,针对每一类运算符分别处理,这样核心算法就不会涉及到具体的运算符,只会涉及到运算符的类别。这样以后如果要扩展更多的运算符就会很方便,不需要再修改核心算法的代码,只需要修改运算符集合类就可以了。

     用一个枚举类表示运算符的种类,代码如下:

 /// <summary>
    /// 运算符类型
    </summary>
    enum OpeType
    {
        <summary>
         该运算因子不是运算符
        </summary>
        notOperator, 常数或变量(没有参数的运算符)
                noparameter,1)"> '+'或'-'
         这两个运算符即可以当正负号使用又可以当加减运算符使用
                PlusOrMinus,1)"> 参数全在左边的运算符
                left,1)"> 参数全在右边的运算符
                right,1)"> 左边和右边都有参数的运算符
                bothSides,1)"> 左括号
                leftParenthesis,1)"> 右括号
                rightParenthesis,1)"> 分隔符,这里指逗号
                Separator
    }
View Code

 

    3、对表达式的进行合法性检查的思路。

    对表达式的合法性检查,我没有学过编译原理,不懂什么是文法分析、词法分析。这个程序检查表达式的合法性的原理是:针对每一类运算符,分别进行判断,检查其参数的个数,参数是在右边还是在左边等等,主要代码如下:

#region 第四次处理
            //第四次处理,判断表达式是否合法,并在合适的地方插入乘号
            for (int i = 0; i < valList.Count - 1; i++)
            {
                #region 当前运算因子是数字
                if (valList[i].type == ValType.Number)数字
                {
                    if (valList[i + 1].type == ValType.Operator)右边是运算符
                    {
                        switch (valList[i + 1].OpeType)
                        {
                            case OpeType.bothSides:正确
                                break;
                            case OpeType.left:case OpeType.leftParenthesis:
                                Insert(opeList.Getope("*"),i + );
                                 OpeType.noparameter:
                                Insert(opeList.Getope(case OpeType.PlusOrMinus: OpeType.right:
                                Insert(opeList.Getope(case OpeType.rightParenthesis:case OpeType.Separator:;
                        }
                    }
                    else右边是数字
                    {
                        Insert(opeList.Getope();
                    }
                }
                #endregion

                #region 当前运算因子是运算符
                if (valList[i].type == ValType.Operator)运算符
#region 当前运算符右边是运算符
                    switch (valList[i].OpeType)左运算符
                        {
                            #region case OpeType.bothSides:左运算符参数信息
                            左运算符参数信息
                                1].OpeType)右运算符
                                {
                                     OpeType.bothSides:
                                        MakeException(i);
                                        ;
                                     OpeType.left:
                                        MakeException(i);
                                        case OpeType.leftParenthesis:正确
                                        case OpeType.noparameter: OpeType.PlusOrMinus:
                                        MakeException(i);
                                        case OpeType.right: OpeType.rightParenthesis:
                                        MakeException(i);
                                         OpeType.Separator:
                                        MakeException(i);
                                        ;
                                }
                                #endregion

                            #region case OpeType.left: OpeType.leftParenthesis:
                                        Insert(opeList.Getope();
                                         OpeType.noparameter:
                                        Insert(opeList.Getope( OpeType.right:
                                        Insert(opeList.Getope(case OpeType.Separator: #region case OpeType.leftParenthesis: OpeType.bothSides:
                                        int pos = i + 1 >= 0 ? i +  : i;
                                        MakeException(pos);
                                         OpeType.left:
                                        pos = i +  OpeType.rightParenthesis:
                                        pos = i - 0 ? i -  OpeType.Separator:
                                        pos = i - #region case OpeType.noparameter:#region case OpeType.PlusOrMinus:#region case OpeType.right: OpeType.noparameter:
                                        MakeException(i);
                                         OpeType.right:
                                        MakeException(i);
                                        #region case OpeType.rightParenthesis:#region case OpeType.Separator: OpeType.bothSides:
                                        MakeException(i +  OpeType.left:
                                        MakeException(i + #endregion

                        }
                    }
                    #endregion

                    #region 当前运算符右边是数字
                    else 1].type == ValType.Number)运算符右边是数字
switch (valList[i].OpeType)
                        {
                             OpeType.left:
                                Insert(opeList.Getope( OpeType.right:
                                MakeException(i);
                                 OpeType.rightParenthesis:
                                Insert(opeList.Getope(

                }
                

            }
            #endregion
View Code

 

 

三、下面是表达式计算过程的主要步骤:

1、将字符串表达式中的数字、运算符、括号、逗号等提取出来,按顺序存储在一个动态数组中。

    原理很简单,就是通过字符串搜索来实现,扫描字符串表达式,依次把运算符取出来放到动态数组中,运算符之间的内容就是参与运算的数了。

    把字符串表达式转换成运算符、括号、逗号和数字的动态数组后,后面将处理这个数组,完成运算。

    部分代码如下:

  #region 将字符串表达式转换成运算因子(数字、常数及运算符)列表
         将字符串表达式转换成运算因子列表
        </summary>
        <param name="str">字符串表达式</param>
        private void ToList(List<CalVal> valList,string str)
        {
            do
            {
                字符串中首个运算符的位置
                int opePos = GetopePos(str);

                CalNum num = null;
                CalOpe ope = ;

                if (opePos > 0)找到运算符且它前面有数字
                {
                    num = GetNum(ref str,opePos);
                }
                if (opePos == 找到运算符且它在字符串最前面
                {
                    ope = Getope( str);
                }
                没有找到运算符
 str);
                }

                if (num != )
                {
                    valList.Add(new CalVal(num));
                }

                if (ope !=  CalVal(ope));
                }
            } while (str != "");
        }
        #endregion

        #region 获取字符串中首个运算符或常数的位置
         获取字符串中首个运算符或常数的位置
        字符串</param>
        <returns>值若为-1表示未找到</returns>
        int GetopePos( str)
        {
            CalOpeList opeList =  CalOpeList();

            int pos = -;
            0; i < opeList.count; i++int k = -int opeIndex = str.IndexOf(opeList.opeList[i].tag);

                科学计数法的情况:如果'+'或'-'的前面是'E'
                if (opeList.opeList[i].opeType == OpeType.PlusOrMinus
                    && opeIndex - 0
                    && str.Substring(opeIndex - 1,1) == E")
                {
                    从'+'或'-'的后面重新查找
                    k = str.Substring(opeIndex + ).IndexOf(opeList.opeList[i].tag);

                    if (k != -1)如果找到
计算该运算符的位置
                        k += opeIndex + ;
                    }
                }
                
                {
                    k = opeIndex;
                }

                if (pos == -)
                {
                    pos = k;
                }
                if (k >= 0 && k < pos)
                {
                    pos = k;
                }
            }

            return pos;值若为-1表示未找到
        }
        #region 获得运算符
         获得运算符
        </summary>
        private CalOpe Getope(ref  CalOpeList();
            CalOpe ope = ;

            if (str.IndexOf(opeList.opeList[i].tag) == 0)
                {
                    ope = opeList.opeList[i];
                    更新str
                    str = str.Substring(opeList.opeList[i].tag.Length);
                    ;
                }
            }

            return ope;
        }
        #region 获取数字
         获取数字
        private CalNum GetNum(string str,1)">int opePos)
        {
            CalNum result = ;

            result = new CalNum(str.Substring(,opePos));

            更新str
            str = str.Substring(opePos);

             result;
        }

         str)
        {
            CalNum result =  CalNum(str);

            更新str
            str =  result;
        }
        #endregion
View Code

 
2、优先级的处理。

    优先级的处理是表达式计算过程中的一个关键问题。
    动态数组中的每个元素(运算符、分隔符或数字等)都有一个优先级,对存储表达式的动态数组从前往后扫描,遇到左括号,将运算符的优先级提升,遇到右括号的话,将运算符的优先级降低。这样处理过后,动态数组中的运算符的优先级各不相同,括号中的运算符的优先级就会高于括号外的运算符的优先级。比如括号外的加号的优先级低于括号内的加号的优先级,因为在处理的过程中把括号内的加号的优先级提升了。

 优先级处理,提高括号中的算式的运算符的优先级
        void LevelProcess()
        {
            int delLevel = 0;优先级增量

            0; i < valList.Count; i++if (valList[i].type == ValType.Operator)
                {
                    如果是左括号
                    if (valList[i].OpeType == OpeType.leftParenthesis)
                    {
                        delLevel += 1000;
                        continue;
                    }

                    如果是右括号
                     OpeType.rightParenthesis)
                    {
                        delLevel -= ;
                    }

                    valList[i].level = valList[i].ope.level + delLevel;
                }
            }
        }
View Code

 
3、在计算存储在动态数组中的表达式前,先生成优先级列表,并按优先级从高到低排好序。

 #region 生成优先级列表
         生成优先级列表
         MakeLevelList()
        {
            #region 生成优先级列表,优先级按从高到低存储
            逐个扫描运算因子,提取优先级列表,并按优先级从高到低存储
            如果是运算符
                如果是括号或分隔符
                     OpeType.leftParenthesis
                        || valList[i].OpeType == OpeType.rightParenthesis
                        || valList[i].OpeType == OpeType.Separator)
                    {
                        ;
                    }
                    InsertIntoLevelList( levelList,valList[i].level);
                }
            }
            
        }
        #endregion

#region 将优先级插入到优先级列表中
         将优先级插入到优先级列表中
        <param name="levelList">优先级列表<param name="level">要插入的优先级void InsertIntoLevelList(ref List<int> levelList,1)"> level)
        {
            列表为空的情况
            if (levelList.Count == )
            {
                levelList.Add(level);
                ;
            }

            该优先级是否已存在
            0; i < levelList.Count; i++该优先级已存在
                if (level == levelList[i])
                {
                    int k = for (; k < levelList.Count; k++if (level > levelList[k])
                {
                    levelList.Insert(k,level);
                    ;
                }
            }
            if (k >= levelList.Count)
            {
                levelList.Add(level);
            }
        }
        #endregion
View Code

 

4、遍历优先级列表,按照优先级从高到低,扫描表达式动态数组,先计算优先级高的运算,用计算结果替换掉计算之前的运算符和参与运算的数。当把优先级列表中的优先级都处理完,对表达式的计算也就结束了,这时,表达式动态数组中将只剩下计算结果。

    下面是计算过程的主要代码

#region 核心算法,对于当前优先级,扫描运算因子列表并计算
                if (levelList.Count != 优先级列表不为空
#region 处理当前优先级
                    对于当前优先级,逐个扫描运算因子,处理具有该优先级的运算
                    )
                    {
                        找到具有该优先级的运算符
                         ValType.Operator
                            && valList[i].level == levelList[0])该运算因子的优先级等于当前优先级
bool isSign = false;Sign为true表示当前运算因子是正负号
                            currentPos = valList[i].primalPos;记录当前运算因子在列表中的原始位置

                            参数列表
                            List<CalNum> numList = new List<CalNum>();
                            临时计算结果
                            CalNum num;

                            i表示该运算因子的位置
                            {
                                #region case OpeType.PlusOrMinus:
                                 OpeType.PlusOrMinus:
                                    若该运算符前面没有运算因子
                                    或前一个运算因子是运算符,则按正负号计算
                                    if (i == 0 || (i - 0 && valList[i - 1].type == ValType.Operator))
                                    {
                                        isSign = true;是正负号
                                        获取该运算符的参数
                                        if (i + 1 < valList.Count && valList[i +  ValType.Number)
                                        {
                                            注意,下面第一句不可省略
                                            numList = ();

                                            numList.Add(valList[i + ].num);
                                        }

                                        计算
                                        num = CalOpeList.Calculate(valList[i].ope,numList);
                                        更新运算因子列表
                                        ReplaceVal(num,1)">2i无需调整
                                    }
                                    若前一个运算因子是操作数,则按加减号计算
                                    {
                                        if (valList[i -  ValType.Number
                                            && i + ();

                                            numList.Add(valList[i - ].num);
                                            numList.Add(valList[i + 3,i - 调整i
                                        i--;
                                    }
                                    ;
                                #endregion

                                #region case OpeType.bothSides:
                                 OpeType.bothSides:
                                    获取该运算符的参数
                                    if (i >= 1 && valList[i -  ValType.Number
                                        && i +  ValType.Number)
                                    {
                                        注意,下面第一句不可省略
                                        numList = ();

                                        numList.Add(valList[i - ].num);
                                        numList.Add(valList[i + ].num);
                                    }

                                    计算
                                    num =更新运算因子列表
                                    ReplaceVal(num,1)">);
                                    调整i
                                    i--#region case OpeType.left:
                                 OpeType.left:
                                    2,1)">#region case OpeType.noparameter:
                                 OpeType.noparameter:
                                    注意,下面第一句不可省略
                                    numList = ();

                                    #region case OpeType.right:
                                 OpeType.right:
                                    #region 该运算符只有一个参数,且它的右边是常数
                                    1 < valList.Count
                                        && valList[i].ParameterNumber == 1
                                        && valList[i + 获取该运算符的参数
                                        ();
                                        numList.Add(valList[i + ].num);

                                        #endregion

                                    #region 该运算符不是只有一个参数,或者它的右边不是常数
                                    
                                    {
                                        计算参数个数
                                        int count = ;

                                        int k = i + ;
                                        从运算符后面开始检测
                                        如果是左括号、分隔符或数字,则执行while中语句
                                        while (k < valList.Count
                                            && (valList[k].type == ValType.Number
                                            || valList[k].OpeType == OpeType.Separator
                                            || valList[k].OpeType == OpeType.leftParenthesis))
                                        {
                                            如果是数字,参数个数加1
                                            if (valList[k].type == ValType.Number)
                                            {
                                                count++;
                                            }
                                            k++;
                                        }

                                        ();

                                        从该运算符后面,逐个扫描,获取该运算符的参数
                                        j表示已读取的参数个数
                                        m表示检测位置增量
                                        int m = int j = 0; j < count; )
                                        {
                                            如果找到数字,存为参数
                                            if (valList[i + j + m +  ValType.Number)
                                            {
                                                numList.Add(valList[i + j + m + ].num);
                                                j++;
                                            }
                                            如果是分隔符或左括号,检测位置增量加1,表示跳过该运算因子,继续检测
                                            1].OpeType == OpeType.Separator
                                                || valList[i + j + m +  OpeType.leftParenthesis)
                                            {
                                                m++;
                                            }
                                        }

                                        更新运算因子列表,count+m+2中的2表示运算符本身和右括号
                                        ReplaceVal(num,count + m + ;

                                

                            }end switch

                            if (!isSign)如果不是正负号
break;退出for循环
                            }
                        }
                    }
                    #region 删除处理完的优先级
                    bool levelExists = 是否存在具有该优先级的运算符
                    逐个扫描运算因子,判断是否仍存在具有该优先级的运算符
                    if (levelList[0] == valList[i].level)存在
                        {
                            levelExists = trueif (!levelExists)该优先级已处理完则删除
                    {
                        levelList.RemoveAt();
                    }
                    #endregion
View Code

 

四、结束语。

    没有什么高深的思想,我用最笨的办法实现了表达式计算。这个程序中用的最多的算法就是字符串查找,以及遍历数组。

 

    程序截图:

    

    GitHub地址:https://github.com/0611163/ScientificCalculator.git

    源代码下载:http://pan.baidu.com/s/1jGyV45w

    从博客园下载:博客园下载地址

 

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐