支持函数,变量的算术表达式计算(一、计算后缀表达式)

12 + sin(sqr(9) + 9) + abs(-90)
给这么一段字符串给你, 要如何才能正确计算出它的值? (注: 值应为 103 )

算术表达式的计算有几种方法,本文只讨论“后缀表达式(也叫'逆波兰表达式')方法”
后缀表达式是啥意思呢? 顾名思义,就是操作符在操作数的后面,比如 12+36 转换为后缀表达式后就变成
12 36 +

注:本文中的后缀表达式是用 List 存储,当然用 Stack 也可以(可能用栈会更好)

后缀表达式可以将复杂的算术表达式变得很简单,它的计算逻辑为
1.遍历整个后缀表达式
2.如果后缀表达式当前节点是数字,则跳过,继续往下遍历
3.如果后缀表达式当前节点是操作符,则将前两个节点取出,用当前操作符作运算
  计算完后,将结果存入当前结点, 并删除前两个操作数节点
重复上面三个过程,直到节点数为 1 (此节点的数据即为最终数据)

我们先来模拟一下 12+36*9 这个表达式的计算
先转换为后缀表达式  12 36 9 * +  (转换方法见后续文章)

1. 遍历表达式直到遇见操作符 *  (第4个节点)
2. 取出前两个操作数(36 和 9) 和 * 作运算,结果存入当前节点
   执行完此步骤后,后缀表达式为
   12 324 +
3. 再重复1,2两步骤,即可得到最终结果  336 , 是不是很简单呢?

下面我们来构造一个可计算后缀表达式的类
先定义节点类型枚举

    /// <summary>
    
/// 节点类型
    
/// </summary>

    public enum TokenType
    
{
        
/// <summary>
        
/// 操作数
        
/// </summary>

        Numeric,
        
/// <summary>
        
/// 操作符
        
/// </summary>

        Operator
    }

操作符枚举

    public enum OperatorType
    
{
        Plus, 
//"+",
        Subtract,//  "-",
        MultiPly,//  "*",
        Divide, //"/",
        
//后面可继续添加函数等
    }


节点类

    /// <summary>
    
/// 节点类
    
/// </summary>

    public class ExpressionToken
    
{
        
private TokenType type;

        
/// <summary>
        
/// 节点类型
        
/// </summary>

        public TokenType Type
        
{
            
get return type; }
            
set { type = value; }
        }

        
private object data;

        
/// <summary>
        
/// 节点数据
        
/// </summary>

        public object Data
        
{
            
get return data; }
            
set { data = value; }
        }


        
/// <summary>
        
/// 构造函数
        
/// </summary>
        
/// <param name="type">节点类型</param>
        
/// <param name="data">节点数据</param>

        public ExpressionToken(TokenType type, object data)
        
{
            
this.type = type;
            
this.data = data;
        }

    }

表达式计算类

    public class Expression
    
{
        
/// <summary>
        
/// 表达式节点列表
        
/// </summary>

        List<ExpressionToken> lstExp = new List<ExpressionToken>();

        
string strExpression;
        
public Expression(string exp)
        
{
            
if (exp == null)
                
throw new ArgumentNullException();
            strExpression 
= exp;
        }


        
/// <summary>
        
/// 开始计算
        
/// </summary>
        
/// <returns></returns>

        private object CalcInner(List<ExpressionToken> exp)
        
{
            
int index = 0;
            
//储存数据
            List<decimal?> digit = new List<decimal?>();
            
while (index < exp.Count)
            
{
                
if (exp.Count == 1 && exp[0].Type == TokenType.Numeric)
                    
break;
                ExpressionToken token 
= exp[index];
                
switch (token.Type)
                
{
                    
//如果是数字,则将值存入 digit 中
                    case TokenType.Numeric:
                        digit.Add(Convert.ToDecimal(exp[index].Data));
                        index
++;
                        
break;
                    
case TokenType.Operator:
                        
//二元表达式,需要二个参数, 如果是函数的话,可能需要更多的参数
                        int paramCount = 2
                        
//计算操作数的值
                        if (digit.Count < paramCount)
                        
{
                            
throw new ExpressionException("缺少操作数");
                        }

                        
//传入参数
                        decimal?[] data = new decimal?[paramCount];
                        
for (int i = 0; i < paramCount; i++)
                        
{
                            data[i] 
= digit[index - paramCount + i];
                        }

                        
//将计算结果再存入当前节点
                        exp[index].Data = CalcOperator((OperatorType)token.Data, data);
                        exp[index].Type 
= TokenType.Numeric;
                        
//将操作数节点删除
                        for (int i = 0; i < paramCount; i++)
                        
{
                            exp.RemoveAt(index 
- i - 1);
                            digit.RemoveAt(index 
- i - 1);
                        }

                        index 
-= paramCount;
                        
break;
                    
default:
                        
break;
                }

            }

            
if (exp.Count == 1)
            
{
                
switch (exp[0].Type)
                
{
                    
case TokenType.Numeric:
                        
return exp[0].Data;
                    
default:
                        
throw new ExpressionException("缺少操作数"1002);
                }

            }

            
else
            
{
                
throw new ExpressionException("缺少操作符或操作数"1002);
            }

        }

        
public object Calc()
        
{
            
return CalcInner(lstExp);
        }


        
/// <summary>
        
/// 计算表达式的值
        
/// (因为不确定参数有几个,所以用数组传进来)
        
/// 比如,函数可能只需要1个参数,也可能需要3个参数
        
/// </summary>

        public object CalcOperator(OperatorType op, decimal?[] data)
        
{
            
//暂只编号写基本四则运算的代码,无函数计算
            decimal? d1 = data[0];
            
decimal? d2 = data[1];
            
if (d1 == null || d2 == null)
                
return DBNull.Value;
            
switch (op)
            
{
                
case OperatorType.Plus:
                    
return d1 + d2;
                
case OperatorType.Subtract:
                    
return d1 - d2;
                
case OperatorType.MultiPly:
                    
return d1 * d2;
                
case OperatorType.Divide:
                    
if (d2 == 0)
                        
throw new DivideByZeroException();
                    
return d1 / d2;
            }

            
return 0;
        }

}

里面的一些基本函数不作赘述。

该函数使用很简单,只有个Calc方法
Expression exp = new Expression("12+36*9");
object result = exp.Calc()); //当然,现在还没转换后缀表达式功能,所以无法计算 :)

   ...........待续

posted @ 2007-12-29 11:29 在天空飞翔 阅读(1575) 评论(15)  编辑 收藏 所属分类: C#

  回复  引用  查看    
#1楼 2007-12-29 12:17 | Tony Qu      
原来有兄弟和我有一样的嗜好,我原来也做过一个科学计算器,你可以去我的blog上看看,多多交流:)
  回复  引用  查看    
#2楼 2007-12-29 12:36 | 毁于随      
数据结构的表达式求值呀.
  回复  引用  查看    
#3楼 2007-12-29 12:37 | LiuXiongfei      
不错 是语义分析中的属性部分吧 似曾相识
  回复  引用  查看    
#4楼 2007-12-29 12:45 | Phinecos(洞庭散人)      
中缀转后缀,后缀表达式计算,数据结构最基础的题目了
  回复  引用  查看    
#5楼 2007-12-29 13:46 | Anders Liu      
:)
  回复  引用  查看    
#6楼 2007-12-29 14:03 | BlueMountain      
两个栈,分别存放运算符合操作数,还有注意()这个东西,大学时候的基本知识了。
  回复  引用  查看    
#7楼 2007-12-29 14:52 | Phinecos(洞庭散人)      
这种题目没啥意思了,有时间还不如找个OnlineJuedge做做ACM的题目练练还好点,
  回复  引用    
#8楼 2007-12-29 15:18 | Joanna [未注册用户]
非常感谢...正在找这个东西...可我有点没看明白...
strExpression字符串内容如何分解到List<ExpressionToken>中的呢...
还有关于函数的处理,我好象没看到相关代码...如果是自定义的函数或object.Fuction()这种也可以吗?
  回复  引用    
#9楼 2007-12-30 09:53 | NetCare [未注册用户]
VS2008TrainingKit里面有个例子好像是Demo目录里面的第11个例子就有一个很常强大的表达式计算功能。可以直接计算2sinx^3子类的东西。
  回复  引用  查看    
#10楼 [楼主]2007-12-30 11:07 | 飞翔天空      
@Joanna
见后续文章

自定义函数不支持, 但修改代码加入另一个函数还是很简单的。

  回复  引用  查看    
#11楼 [楼主]2007-12-30 11:08 | 飞翔天空      
@NetCare
这个类也能计算
  回复  引用  查看    
#12楼 2007-12-30 15:15 | dikongpulu      
拼计算字符串然后动态执行不是更方便实现?
  回复  引用  查看    
#13楼 2007-12-31 14:12 | 金色海洋(jyk)      
一般地思路都是 中缀转后缀,再对后缀表达式来计算。

但是我的思路是只遍历一遍正常的计算式(中缀),然后直接得到结果。

记得我当年的课程设计就是这个。
  回复  引用    
#14楼 2008-01-22 13:31 | jeasonzhao [未注册用户]
我采用的方法和你的不一样,
先做词法分析,然后再做表达式构造,构造出一颗二叉树,对这个二叉树的每个节点开始求值
求值的时候引入一个外部接口,这个接口有几个函数
ValuePair GetVar(String strVarName) 返回指定变量的值
FunctionSignWrapper GetFunction(String strFunctionName,Object[] parmaeters)
这样我的函数、变量都可以在实现的接口中扩充,呵呵
  回复  引用  查看    
#15楼 2008-03-10 17:37 | 簡簡單單..      
...

标题  
姓名  
主页
Email (只有博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2007-12-30 11:05 编辑过


相关链接: