需求
实现一个类库来存储数学表达式。
该类主要实现以下功能:
- 以某种数据结构存储一个数学表达式。(包含加减乘除,幂,对数等运算符)
- 将一个字符串识别为数学表达式。
- 将存储的表达式转换为字符串或浮点数值。
要求输入的表达式有如下的形式。
加减乘除幂分别用字符 '+', '-', '*', '/' 来表示,其左右操作数分别放在该字符串的两边。如 1+1, 2^3 等。
对数要将其底数与指数放在括号中,并以逗号分开。 如 Log23 应表示为 Log(2,3)
以 10 为底的对数应将 10 显示的写出。 如 Log10100 应表示为 Log(10,100)
另外提供自然对数 Ln。 如 Ln100 可以表示为 Log(2.71828,100) 或者 Ln(100)
上面的操作符均允许嵌套。
设计
要求输入的表达式有如下的形式。
加减乘除幂分别用字符 '+', '-', '*', '/' 类表示
一共设计两个类和一个枚举类型。
如图:
下面那个是用来测试的控制台程序
枚举 类型OperType用来表示几种操作符;
类 Operator是操作符类,封装了基本的操作符以及常用的方法;
类NumClass是数类,存储表达式并提供表达式的相关操作。
NumClass 类是核心类。
存储数学表达式的数据结构是"树"。对于每个表达式将其按照操作符划分,并以操作符为父节点,左右操作数分别为左右子树。
其主要方法有:
- 构造函数 NumClass(String num); 从字符串转化为表达式类
-
求值函数 string ToString(); 从存储在类中的表达式向字符串的转化
double ToDouble(); 对存储的表达式的求值
- 化简函数 void Trim(); 在一定程度上化简表达式
实现
上面三个函数的实现思路与算法。具体实现看后面附的程序。
NumClass(String num)
这是一个递归函数。
在字符串中找到适当的位置(要考虑到操作符的优先级)来划分左右子树。把该位置左边递归给左子树,右边递归给右子树,中间的是操作符。
若上面找到的"适当的位置"左边只有一个操作符(或操作数)则执行下面的操作,否则返回。
函数检查字符串最左边的字符,确定表达式第一个操作数(或者是操作符)是数字,括号,对数还是正负号等。并分别赋予正确的值。这样便完成了一次递归的操作。
由于表达式中有形如 Log Ln 等不规则的字符。故实现起来有很多细节需要注意,具体见程序。
string ToString()
double ToDouble()
也是递归实现。
这两个函数的实现方法比较类似。都是递归的计算左右子树,并把结果和操作符一起返回给上一层的调用。
ToSTring() 方法要注意判断操作的优先级以决定是否要增加括号。
void Trim()
该函数调用一个私有函数 void RecursionTrim()。后者递归调用自身,并通过检查操作符的类型以及左右子树是否是叶子结点来决定是否要化简。
可以化简4类表达式。
- 不失精度的操作,包括 '+', '-', '*'。如 (1+1)*4 最后可以化简为 8;
- 同底数的对数相加减。如 Log(c,a) + Log(c,b) = Log(c,a*b);
- 同底数的幂相乘除。 如 c^a * c^b = c^(a+b)
- 同指数的幂相乘除。 如 a^c * b^c = (a*b)^c
测试
新建了一个控制台应用程序来测试上面写好的类库。
列举一部分。具体见代码。
构造函数测试 |
NumClass num;
num = new NumClass("0"); num = new NumClass("-1"); num = new NumClass("+2");
num = new NumClass("-1*2"); num = new NumClass("-1+2"); num = new NumClass("+2*3");
num = new NumClass("((1+1)*(2+2))"); num = new NumClass("log(10,100)*(2+2)/log(2,4)"); num = new NumClass("log(3+7,10*10)^4/log(4,16)"); num = new NumClass("log(log(3,9),log(10,100))"); num = new NumClass("2+((log(log(3,9),log(10,100))+4*2)^2)"); num = new NumClass("log(2+3,25*5)/log(5*5,625)+log(25,625)-log(5,25)"); num = new NumClass("log(1+5,36^2)/log(1+4*2,729)^2+log(3^2,3^4)"); num = new NumClass("log(log(1+1,4^2),16^3)/log(5,log(2,2^25))"); num = new NumClass(" log(log(1+1,4^2), 16^3) / log(5, log(2,2^25)) ");
num = new NumClass(0); num = new NumClass(0.3); num = new NumClass(-2); num = new NumClass(123456.7890);
Console.WriteLine(num.ToDouble()); Console.WriteLine(num.ToString()); |
操作符重载测试 |
NumClass numTest1; NumClass numTest2; NumClass numTest3;
numTest1 = new NumClass("1-2"); numTest2 = new NumClass("3+4"); numTest3 = numTest1 + numTest2; numTest3 = numTest1 + 4; numTest3 = "3-(2*8)" + numTest2;
numTest1 = new NumClass("1-2"); numTest2 = new NumClass("3+4"); numTest3 = numTest1 ^ numTest2; numTest3 = numTest1 ^ 4; numTest3 = "3-(2*8)" ^ numTest2;
Console.WriteLine(numTest3.ToDouble()); Console.WriteLine(numTest3.ToString()); |
表达式化简测试 |
NumClass num;
num = new NumClass("0"); num = new NumClass("-1"); num = new NumClass("+2"); num = new NumClass("-1*2"); num = new NumClass("-1+2"); num = new NumClass("+2*3");
num = new NumClass("Log(2,2) + Log(2,3)"); num = new NumClass("Log(2,2^4) + Log(2,10)"); num = new NumClass("Log(4,2*4) - Log(2+2,10*4)");
num = new NumClass("2^3*3^3"); num = new NumClass("2^3/3^3"); num = new NumClass("2^4*2^5"); num = new NumClass("2^4/2^5"); num = new NumClass("Log(2,3)^4*Log(2,3)^4"); num = new NumClass("2^Log(2,3)/2^Log(2,3)"); num = new NumClass("2^Log(2,3)/2^Log(2,3)^3");
Console.WriteLine("Before Trim:"); Console.WriteLine(num.ToDouble()); Console.WriteLine(num.ToString()); Console.WriteLine("After Trim:"); num.Trim(); Console.WriteLine(num.ToDouble()); Console.WriteLine(num.ToString()); |
缺陷
- 测试不够完善,程序中肯定有很多未知的错误。
- 对错误的输入格式的检查。程序中遇到不正确的输入时便抛出一个异常,但没有机制接收异常。
- 化简机制不够完善。比如类似于 4/3/3/3/3/3/3*3*3*3*3 的表达式不能化简为4。