我们要做的是对单变量多项式的括号展开,并且化简输出,所以我的思路为分为两步:
首先,用递归下降法解析输入,将输入的表达式进行化简和拆解,得到一个不含括号的后缀表达式字符串。接着再按照多项式运算规则进行运算并合并同类项,最后结果进行排序,最后输出结果。
我的代码分为了两大部分,分别实现两种相互独立的功能,并用main函数实现连接
1.
递归下降法求后缀表达式
首先对表达式进行预处理
去除所有空白字符
若第一项为符号,则在表达式开头补 0
在表达式中连续的 +/-
号,整合成一个 +/-
号
在表达式中 **
后面紧跟着的 +
号,删除之
通过利用形式化表述定义,将表达式分成了四个层次:因子(常量或变量)、基本项(幂)、项(乘除)、表达式(加减),这四种结构对应了四种对象。我们下面分别进行讨论对这些对象的解析:
[加减] 因子 ** 带符号整数
。当解析到一个因子后,若下一个解析到的为 **
,那么读入这个符号并做处理,然后下一个符号一定是一个带符号整数,否则,说明基本项的解析结束了。[加减] 因子 | 项 * 因子
,处理方法与基本项相似。对表达式(加减)的解析:表达式由项和加减符号组成,即为:[加减] 项 | 表达式 加减 项
通过题目所给的形式化表述可以表叫容易地建立层次,并且通过方法的互相调用完成解析。递归下降方法的好处在于,它可以通过方法之间的间接递归调用,非常自然地处理嵌套的表达式(即带嵌套括号的表达式)。
2.
后缀表达式计算
第1步中所得的后缀表达式中,运算符为 +/-/*/**
,操作数为 带符号整数 / 变量x
。
把所有操作数和运算结果视为一系列单项式之和(多项式)
则:
带符号整数
,视为指数为零,系数为带符号整数的值的单项式变量x
,视为指数和系数均为 1 的单项式由于后缀表达式的便利性,因此我们每读到一个运算符,就对最近的两个多项式进行对应运算并返回一个多项式即可
Lexer
操作数/运算符
+/-
号(如果有)是否是单目运算符)Parser
(
则递归调用 parseExpression()
先处理括号内的内容即可Item
+/-/*/**
操作均在该类中使用公开函数实现,需要时根据运算符类型调用即可toString
方法,对保存在该类的各单项式对指数项进行排列后输出Factor
Expression
表达式
+
和 -
地位相同,所以让该类自带一个队列(此处用 ArrayList
实现,并用 flag
标记队首toString
方法,把保存在该类中的数逐个输出并用 +/-(队首元素)
相连Term
项
toString
方法,把保存在该类中的数逐个输出并用 *
相连Basic
基本项
toString
方法,把保存在该类中的数逐个输出并用 **
相连Constant
常量因子
Variable
变量因子
总UML类图
代码规模分析
方法复杂度分析
类复杂度分析
优化小方法:
x**2
用 x*x
替换本次作业是在第一次作业的基础之上对多项式的括号展开,并且化简输出,相比于第一次作业,本次作业新增了三种函数,以及拓展了一些数据显示或者实现上的细节。因此,在第一次作业的基础上,进行迭代开发,将新增要求一一实现即可。
三角函数:
自定义函数
求和函数
指数变更:
括号嵌套:
SumFun
sum(i, 1, 3, (i*x))
处理为 ((1*x)+(2*x)+(3*x))
replaceAll
的小心 "sin"
里面也有个"i"
sum
的上下限需要用 BigInter
SelfDefineFun
addFun
方法读入一个自定义函数表达式,以 HashMap<String, ArrayList<String>>
的形式保存于该类中f(x,y,z)=x+y**2+z**3
存储为 HashMap<f,[x, y, z, (x+y**2+z**3)]>
自定义函数为:f(x,y,z)=x+y**2+z**3 ; 待处理表达式为:f(sin(x)**2,cos(x),x) ;
处理为 ((sin(x)**2)+(cos(x))**2+(x)**3)
x
以免出现 x
的重复代入TriFun
Experssion类
之上,具有 addExpression
方法用于递归sin
/ cos
)toString
方法,把保存在该类中存储的表达式并输出三角函数类型(视为单目运算符)TriSimplify
Item
类中符合三角函数化简规则的项进行运算并化简,返回一个处理后的 Item
类Lexer
sin
和 cos
识别x
为识别所有自变量、Parser
parseTriFun
方法)x
为识别所有自变量Item
x
的计算,在本次作业中要涉及多变量计算(把三角函数作用于表达式后的整体视作一个自变量(原因:无法与 x
合并,具有自己独立的系数和指数) )HashMap<HashMap<String, BigInteger>, BigInteger>
的形式保存于该类中5*x*sin(x**2)**2
保存为 HashMap<HashMap(<x,1> & <sin(x**2),2>),5>
add/sub/mul/pow (+/-/*/**)
方法sin/cos
方法,作用为把表达式用 sin()/cos()
包裹并定义为新自变量总UML类图
代码规模分析
方法复杂度分析
类复杂度分析
优化小方法:
<因子>
的长度为 1
, 则把 <因子>**2
用 <因子>*<因子>
替换。 (Warning:若是三角函数中 "<因子>**2" 则不能应用此变换,原因为不符合本次作业中的形式化表述)
项*sin(<因子>)**2 + 项*cos(<因子>)**2
\(\rightarrow\) 项
sin(0)
\(\rightarrow\) 0
, cos(0)
\(\rightarrow\) 1
sin(-<因子>)
\(\rightarrow\) -sin(<因子>)
, cos(-<因子>)
\(\rightarrow\) cos(<因子>)
a + b*sin/cos(<因子>)**2
\(\rightarrow\) (a+b) - b*cos/sin(<因子>)**2
2 * sin(<常数因子>) * cos(<常数因子>)
\(\rightarrow\) sin(<常数因子> * 2)
本次作业是在第一次作业和第二次作业的基础之上新增了一些琐碎的小功能,算是比较小的一次迭代(甚至如果你前几次扩展性写得好可以认为甚至不是一次迭代)
三角因子嵌套:
函数嵌套
数据量增加:
Cost <= 1000
\(\rightarrow\) Cost <= 100000
, 允许出现求和函数SelfDefineFun
check
方法用于检测并处理自定义函数调用中出现的递归调用问题总UML类图
代码规模分析
方法复杂度分析
类复杂度分析
优化小方法:
<因子>
的长度为 1
, 则把 <因子>**2
用 <因子>*<因子>
替换。项*sin(<因子>)**2 + 项*cos(<因子>)**2
\(\rightarrow\) 项
sin(0)
\(\rightarrow\) 0
, cos(0)
\(\rightarrow\) 1
sin(-<因子>)
\(\rightarrow\) -sin(<因子>)
, cos(-<因子>)
\(\rightarrow\) cos(<因子>)
a + b*sin/cos(<因子>)**2
\(\rightarrow\) (a+b) - b*cos/sin(<因子>)**2
2 * sin(<常数因子>) * cos(<常数因子>)
\(\rightarrow\) sin(<常数因子> * 2)
本人三次作业强测及互测结果如图:
第一次作业:
第二次作业:
第三次作业:
总共找出的 bug
数: 1
个
分别为:
1.
sin((sin(x)*x))
sin(sin(x)*x)
sin((sin(x)*x))
计算时三角函数判定内部是否为表达式的方法 IsTerm() 判定条件错误
根据形式化表述撰写 python
程序生成覆盖性测试数据对他人程序随机生成数据来进行黑盒测试
测试有效性: 较佳,互测基本能覆盖所有人被找出的bug
是否结合被测程序的代码设计结构来设计测试用例: 否
本单元是表达式括号展开的从简到难的三次迭代过程,在这次作业中我的面向对象思维方法得以初步的建立:第一单元我掌握了 gitlab
的使用,熟悉了代码风格的检查,重视和逐步掌握了测试方法和技巧;第二单元我掌握了迭代设计,增量开发,意识到一个好的架构设计的重要性;第三单元使我掌握和应用继承、接口和多态机制,以统一的架构来整合了三次作业的功能,并且强化了我基于黑盒测试来定位程序bug的能力。三次作业均强调了鲁棒性设计和层次化设计,规范了我的代码书写风格,让我认识了 "坚持美观,灵活对待,符合编程的一般原则"
的广义代码风格,使我受益匪浅。