1.main类:负责将输入的字符串按照正则表达式将每一项拆分成子字符串,再将字符串数组通过Expression构造类创造一个Expression,再将Expression的对象进行diff;
2.Expression类:由容器ArrayList
存储项。遍历字符串数组创造项,由于第一次作业中的项和因子之前的区别不大,没有划分出因子类,且题目中的嵌套递归表示方法可以直接找出较为简单的项的表达规律,通过正则拆分出项的字符串,直接在传入子字符串通过FunctionFactory类构造项,添加Exponential的时候会遍历并且可以在 指数相同的时候进行合并。
3.工厂类:将子字符串拆分出系数、指数,然后创造出Exponential类
4.Exponential:求导的最小单位
由于在处理字符串得到index后,直接将index子字符串replace掉,导致的问题在于没有考虑到还未处理的字符串后面部分存在一样开头的部分也会被处理掉如:x** -9 * x**-90
,同时在用正则的时候,将指数部分可以是 \+\d
形式有缺漏。此处反思可以将指数的处理归类成整数常量处理方法,而不是直接在factory中用一长段代码进行处理,这样代码的可扩展性会更强。代码行数结构变化不大。
1.main:仅将传入的字符串做除去空格处理,传入工厂
2.Expression:通过ArrayList得到Term的集合;可以通过遍历Term的diff()进行diff();获得表达式中有效项即项系数不为0的项的个数;若有效项个数为1,则可以将这个有效项输出,这个函数是表达式化简的一个重要的步骤 ;可以输入Expression\Term对象和ArrayList进行合并,其中Term.equals()是进行合并化简是重要的步骤。
3.Term:通过ArrayList得到Factor的集合;可以通过遍历Factor的diff()进行diff();其中性质Coeff需要在ArrayList加入新的Factor后不停的更新,且将所有进入ArrayList的Factor的Coeff归一,这样可以在合并时较为简洁快速的处理常数因子或系数的合并。
4.Factor:有类型、系数、指数、表达式,四类性质,因为在这次作业中,四种因子的相同特征更多更明显,主要通过指数、系数和类型,通过不同类型的判断可以由不同的构造函数进行对象创建 ,一个factor涵盖了所有出现的4种因子。但是在toString()函数中显得很冗余,可延展性特别差。
5.StringRecord:改变作业1中通过正则进行项的拆分的方式,运用递归下降的方法,通过工厂将这句表达式顺着捋下去,专门通过一个记录针指向当前字符串读到的位置。
6.Factory:递归下降读入,但是由于在Factor没有合理的规划,导致在创造Factor对象时,需要经过4次对type的条件判断,然后分别进行处理语句、创造。
一个严重的private语法错误:
在编写方法的时候一定要注意参数存在同类参数的情况,在未提交的时候自查,出现了一个bug是由于Term在进行simple()的时候两层循环去寻找同类项合并的时候,直接进行了两层 for(Factor factor : Term_i.ArrayList_factors)
,这直接导致了不可复现bug,因为两层循环调用的都是this的ArrayList的成员。
在编写程序的时候一定要注意递归的使用!!!!警惕TLE!!!:
一个递归过程本身其实没有那么明显直观的导致运行慢,但是,如果在递归(diff())的时候再递归(toString()),或者整个程序不停的进行各种层次没有必要或者可以替换的递归(toString()),会导致40分钟都运行不出来一个嵌套了15个括号的表达式,更何况读入一次生成表达式需要进行递归,读入的表达式求导生成表达式又要进行递归。
合并同类项equals()函数缺漏:
由于各类因子的各类参数混杂在一起,且由于求导的时候没有仔细的处理掉已经求导为0的因子,导致在判断项相同时有很多参照点以及重复重叠的地方,反而导致出现缺漏,没有合理的判断出特殊的同类项。
将Factor作为父类,将Expo,Sin,Cos,Express四种factor分为其子类。这样在创建新的factor以及factor.diff()时可以减少很多类型判断,从而使工厂和factor耦合度降低,更容易进行迭代。但是由于对继承的方法知识了解不够清晰,没有使用接口,且在代码风格中,每一个类的性质是需要private属性的,不可以用protected,所以我重写了父类几乎所有的方法、性质,而且还导致仍然需要在判断了类型后处理diff()等,这完全可以通过接口的方式来做出代码改进。
这一次使用了测评机,提前自刀了很多次,再加上第二次重构了递归下降读入,bug修复阶段改善了之前TLE的情况,以及改进了equals()函数,这次强测和互测没有挨刀。在处理sin,cos函数时为了保险不出错,在最后对里面含有表达式因子进行优化后,再次加了一重括号输出,并且没有对任何三角函数进行化简,只是判断能否相同的项合并,且没有对里面含有表达式因子的三角函数进行合并,所以有一题性能分仅由49分。
所以需要在对项和因子的表达和构造进行精简后,在表达式中增加equals()函数,能够进一步优化求导。
正则表达式中存在纰漏
“+-”号粗暴的合并
括号嵌套TLE
sin\cos内含常数因子求导合并异常
常数因子连乘
第三次作业可以直接将生成的导数迭代求导,一旦出现WF,则在输出时优化出错(超出指数范围除外)
更倾向于生成()嵌套以及sin/cos内含可化简的表达式因子
在第一次代码中,main函数的功能存在一个对输入字符串的正则处理,这完全应该是由其他函数来做的工作,在main函数中,更多承载的功能是链接项目对外的输入和输出,而并非直接操手。
第一次作业中出现了表达式的递归表示,仅仅由x ** d和常数两种的情况下是可以通过正则来完整的表达出项或者表达式的,https://regexper.com/ 就是靠这个网站生de正则的bug,到了第二次看到表达式因子、sin\cos就果断放弃这个方法,选择递归下降,从头到尾捋顺输入的字符串,识别到特定字符可以递归的进行因子、项、表达式的构造。
第二次将所有种类的因子用一个Factor类归类了,这就导致几种因子性质的相同点全部重叠了,x变量本不用express性质也被强行加上了Null,epressFactor本不用index和coeff也被强行安上了1,1;所以整体很臃肿。
ev(G)
:Essentail Complexity 基本复杂度。用来衡量程序非结构化程度的,非结构成分降低了程序的质量,增加了代码的维护难度,使程序难于理解。
iv(G)
Design Complexity模块设计复杂度。用来表示一个方法和他所调用的其他方法的紧密程度,即模块和其他模块的调用关系。软件模块设计复杂度高意味模块耦合度高,这将导致模块难于隔离、维护和复用。
v(G)
是用来衡量一个模块判定结构的复杂程度,数量上表现为独立路径的条数。即合理的预防错误所需测试的最少路径条数,圈复杂度大说明程序代码可能质量低且难于测试和维护,经验表明,程序的可能错误和高的圈复杂度有着很大关系。
OCavg
:类的方法的平均循环复杂度。
WNC
:类的方法的总循环复杂度。
可以明显观察到,在改进了TLE即在Term方法中重复使用toString后,类的最大循环复杂度下降,递归减少。且在第二次到第三次将factor重构后,Factor的以及Term中的循环复杂度下降,对factor的种类判断减少。
第一次类平均循环复杂度最大,由于使用正则表达式将项按照字符串数组拆分出来,因此regularStr()这一函数经常被调用,iv,ev较大。
class | OCavg | OCmax | WMC |
---|---|---|---|
Exponential | 2.3333333333333335 | 9.0 | 14.0 |
Expression | 4.75 | 7.0 | 19.0 |
FunctionFactory | 3.0 | 5.0 | 9.0 |
Main | 3.0 | 3.0 | 3.0 |
Total | 45.0 | ||
Average | 3.2142857142857144 | 6.0 | 11.25 |
(homework1)
第二次由于通过递归下降方法读入,所以Factory循环复杂度较大。在读入、求导时经常使用equals函数对合并同类项进行判断,所以equals()方法耦合度较高。
class | OCavg | OCmax | WMC |
---|---|---|---|
src.Expression | 2.25 | 7.0 | 27.0 |
src.Factor | 2.125 | 8.0 | 51.0 |
src.Factory | 4.142857142857143 | 7.0 | 29.0 |
src.Main | 1.0 | 1.0 | 1.0 |
src.StringRecord | 1.0 | 1.0 | 6.0 |
src.Term | 3.0434782608695654 | 11.0 | 70.0 |
src.Type | 0.0 | ||
Total | 184.0 | ||
Average | 2.5205479452054793 | 5.833333333333333 | 26.285714285714285 |
(homework2)
class | OCavg | OCmax | WMC |
---|---|---|---|
src.CosFactor | 1.6470588235294117 | 8.0 | 28.0 |
src.ExpoFactor | 1.3125 | 4.0 | 21.0 |
src.ExpressFactor | 1.0625 | 2.0 | 17.0 |
src.Expression | 2.25 | 7.0 | 27.0 |
src.Factor | 1.105263157894737 | 2.0 | 21.0 |
src.Factory | 4.625 | 9.0 | 37.0 |
src.Main | 1.0 | 1.0 | 1.0 |
src.RFormatException | 0.0 | ||
src.SinFactor | 1.6470588235294117 | 8.0 | 28.0 |
src.StringRecord | 1.7 | 4.0 | 17.0 |
src.Term | 2.9 | 11.0 | 58.0 |
src.Type | 0.0 | ||
Total | 255.0 | ||
Average | 1.875 | 5.6 | 21.25 |
(homework3)
1->2:改变main函数的功能,改变读入字符串方式即改变整个工厂,改变项的性质,添加因子类型,增加toString函数,增加乘法求导运算规则,增加合并同类项。
2->3:补充读入字符串时在关键部分抛出异常,改变因子原来对各个类型判断后求导,直接通过子类重写方法,无需判断直接用函数。
互测的优点:可以和周围同学讨论优秀的代码,学习其风格,了解更多java灵活的应用,观摩架构。
测评机的好处:debug更加全面,作为一种辅助手段使代码更加抗打,而且效率高。
面向对象的思想不足:写代码时没有完全从对象的角度出发,并没有完全考虑周全它的突出性质和突出的方法是什么,找出共同点、抽象归类能力不够。导致在写类的方法的代码时候,根据临时的小的需求补充与类重要特征无关的方法;对于方法的属性、方法的目的性还是不甚清晰,通常会根据临时的需求改变属性,这一点很不好,下一次会更加慎重的全面的了解题意,分析情况,分配好结构,更加深入的思考方法的目的。
bug:见上述。
正则表达:对于这次的循环嵌套的定义,最开始是很束手无策的,但是后来直接根据题目的定义一遍一遍的嵌套,还是可以较为自然的将题目理解出来,正则表达不需要嵌套定义,只需要通过循环的方式把规律表示出来即可。
原文:https://www.cnblogs.com/pgemz/p/14585524.html