Skip to main content

OO 第一单元总结 表达式求导

OO第一单元通过三次递进式的作业让我们实现表达式求导,在这几次作业中我也有很多收获。下面就回顾一下前三次作业中存在的问题。

在个人看来,表达式求导的难点主要有三部分——对输入的处理、表达式的存储结构以及化简。这三次作业我所采用的表达式存储结构都不相同,不过重构的速度还是比较快的(安慰自己)。

第一次作业

在第一次作业中,我的程序总体架构为提取幂函数为Poly对象,建立Polynomial类解析表达式创建幂函数对象,在主类Derivative中进行部分性能优化工作。

① 程序结构分析

UML类图:

第一次作业的幂函数因子较为简单,代码中仅提炼出Poly对象,并在其内部实现求导方法。

Method复杂度:

Methodev(G)iv(G)v(G)
Derivative.main(String[])111
Derivative.printAnswer(HashMap<BigInteger, Poly>)457
Poly.Poly(BigInteger,BigInteger)111
Poly.addCoef(BigInteger)111
Poly.compareTo(Object)111
Poly.derivative()122
Poly.equals(Object)222
Poly.getCoeff()111
Poly.getIndex()111
Poly.hashCode()111
Poly.toString()11112
Polynomial.Polynomial(String)111
Polynomial.getPolyitem()122
Polynomial.getfirstitem()1910

第一次作业中,化简主要在主类Derivative的printAnswer()方法(将第一个正项优先输出)和Poly类的toString()方法中,printAnswer()涉及对表达式的遍历,基本复杂性高;toString()则包含大量条件语句,多次调用了BigInteger中的方法,循环依赖性高。而表达式解析我采用的是正则+状态机的策略(这个策略三次作业均沿用,感觉还是很舒服的),在getfirstitem()中处理不同类型输入并归一化,代码较为复杂。

② 程序Bug分析

第一次作业在强测、互测中均未出现Bug。

③ 互测采用策略

互测时我采用的策略是“补刀”。我先是用简单的自动测试程序跑了下房间内成员的代码,不过貌似没有出现问题;然而圣杯战争发生了转机——Rider竟然拿下了一血!

于是我一一检查其他成员的代码,发现Berserker的正则存在问题:在正则中匹配的是“[+|-]{0,2}(\\d*.)?x(..[+|-]?\\d+)?”,其中的“.”可匹配任意字符,就会错误解析表达式。

第一次互测时认真读了屋内其他同学的代码,感觉还是很有收获的。之后两次大部分都读不下去,可读性实在是让人肝疼……放到评测姬里自生自灭吧!另外这次互测也让我意识到了高工同学的可怕之处(误),全屋子就这一个Bug,大佬换着花样hack……卷卷更健康。

④ 对象创建模式

在第一次作业中,我并没有应用对象创建模式,表达式解析就顺便new了新因子,Polynomial类的紧耦合比较严重。

第二次作业

第二次作业增加了三角函数因子,这个时候我也偷偷看了一下去年的第三次作业题目,新增加的嵌套对于表达式的化简无疑是不小的挑战。所以在规划第二次作业的总体架构时,我就面临着一个选择——是要建立终将在第三次作业重构的四元组项;还是要采用“表达式-项-因子”三层结构,为第三次作业留好迭代的空间?

经过一番挣扎(其实并没有,因为我还不会写三层结构),我选择了势必带来重构的四元组形式,也就是将表达式的每一项以“coeff*x**powindex*sin(x)**sinindex*cos(x)**cosindex”的形式存储,这样处理方式就和第一次作业基本一致。在基本架构沿用第一次作业的情况下,第二次作业给我最大的挑战反而是三角函数的化简,我的化简类Simplify的行数是最多的。

① 程序结构分析

UML类图

架构基本沿用第一次作业,UML也差不多,不过这次的简化更复杂一些。

Method复杂度:

Methodev(G)iv(G)v(G)
DeriPrinter.DeriPrinter(HashMap<String, MutiItem>)177
DeriPrinter.getAnswer(HashMap<String, MutiItem>)457
DeriPrinter.printAnswer()122
Item.Item(BigInteger,BigInteger,BigInteger)111
Item.compareTo(Object)333
Item.equals(Object)244
Item.getCosin()111
Item.getPowin()111
Item.getSinin()111
Item.hashCode()111
Item.toString()158
MainClass.main(String[])111
MutiItem.MutiItem(BigInteger,BigInteger,BigInteger,BigInteger)111
MutiItem.MutiItem(BigInteger,Item)111
MutiItem.addCoeff(BigInteger)111
MutiItem.compareTo(Object)111
MutiItem.derivate()155
MutiItem.equals(Object)222
MutiItem.getCoeff()111
MutiItem.getCosin()111
MutiItem.getIdentity()111
MutiItem.getItem()111
MutiItem.getPowin()111
MutiItem.getSinin()111
MutiItem.hashCode()111
MutiItem.toString()155
ParseExp.MutiItemSign()234
ParseExp.ParseExp(String)156
ParseExp.WrongFormat(String)111
ParseExp.getExpression()122
ParseExp.getFirstitem()31314
Simplify.Simplify(HashMap<String, MutiItem>)122
Simplify.exitmatch(BigInteger,Item,Item,Item,HashMap<Item, BigInteger>)234
Simplify.searchcos(HashMap<Item, BigInteger>)469
Simplify.simplify()134

由于一开始并没有明确的化简思路,在第一遍实现了基本求导功能的代码通过中测之后,我为了实现化简功能,又对代码内类的结构做了很多修改,在这一过程中,尽管我确实用类将表达式中的Item与MulItem做了封装,但我却胡乱修改内部方法,这并不符合面向对象的思想,也导致最后代码可读性极差,也为互测时被发现的Bug埋下了祸根。具体表现到MetricReloaded的分析上,就是ParseExp类的getFirstitem()依然是复杂度重灾区,没有采用工厂模式也使得它与其他类的依赖极为严重,优化输出的内容依然有很高的基本复杂性。

② 程序Bug分析

强测中获得性能分为16.545/20,在处理表达式化简时,我所采用的是暴力搜索的方式,每次搜索到可以合并的项就将其合并,然后进行重新搜索。

这种方法显然只能找到一个极优解,却不能找到最优解,表达式项存储顺序对结果也有很大影响。一种更加可行的方式是采用深度优先搜索,不过当时并没有想出具体的实现方法,只好作罢。

互测时被找出来一个Bug,同时发现Berserker的一个Bug(狂战士日常躺枪)。而我的Bug是HashMap的Key写错,在生成MulItem的Key时,我采用的是将指数拼接为String的方式,在指数较大时,可能会导致不同项被当作同一项存入HashMap中,造成求导错误。

修改也很简单,只需要在生成String的时候给不同指数加个分隔符就好了——

public MutiItem(BigInteger a, BigInteger b, BigInteger c, BigInteger d) {
this.coeff = a;
this.item = new Item(b, c, d);
// before: identity = "" + b + c + d;
identity = "" + b + "." + c + "." + d;
}

可以发现这里我的MutiItem的构造方法是非常混乱的,不过没有发现这么明显的Bug,一方面就是混乱的优化带来的副作用;另一方面也是因为这样的数据出现几率确实很小,同屋里也只有一个同学发现了这个Bug(我后来和hack我的同学交流了下,果然是长时间跑随机自动测试程序才找到的……)

③ 互测采用策略

在这次互测是采用对拍+手动构造样例+Python生成随机数据自动评测的方式。屋内除我只有一个Bug,大家很快找到之后也比较佛系。

互测中阅读了同屋子大佬处理简化的DFS,确实厉害;另外还拜读了另一个房间Alterego的架构,类似Sympy的表达式结构简直叫人拍案叫绝,计算方式优美,让我看到了什么是真正的封装、多态、继承,也有更多信心去面对第三次作业。(不过听说在那个屋Alterego被hack的很惨,悲)

④ 对象创建模式

在第二次作业中,由于使用的是和第一次作业类似的四元组项,我依然没有应用对象创建模式,表达式解析里又new了新因子,紧耦合依然严重。这在第三次作业中有一定改善。

第三次作业

前两次作业都没有对表达式进行结构化处理,导致了第三次作业表达式的存储结构必须大改。

好在表达式求导的三大难点——对输入的处理、表达式的存储结构以及化简,这次都处理得比较好——

  • 对输入的处理上,有了前两次作业中采用的正则表达式加有限状态机读入的经验,修改很快;
  • 表达式的存储结构上吸取了Alterego的架构(感谢Alterego!!!!),在进行重构的时候思路比较清晰,避免了第二次作业惨剧的重演;
  • 化简上,层次化的表达式存储方式发挥出了它的优越性,通过统一的接口进行因子类型的转换,效果好;

在第三次作业我也首次采用了Package来管理类,也是一个小进步。

① 程序结构分析

UML类图:

这一次的类图很乱,主要原因还是在于表达式也可以作为一个因子嵌套进三角函数中,类之间的相互调用也比较明显。

Method复杂度:

Methodev(G)iv(G)v(G)
homework.MainClass.main(String[])122
homework.expression.ExpFunction.Exception.Exception(String)111
homework.expression.ExpFunction.deleteSpace(String)3710
homework.expression.ExpFunction.matchParentheses(String,int)667
homework.expression.ExpFunction.readIndex(String)212
homework.expression.ExpFunction.simplifyExpParentheses(String)91114
homework.expression.ExpFunction.simplifyParentheses(String,int[])118
homework.expression.ExpFunction.simplifySign(String)434
homework.expression.ExpParser.Exception.Exception(String)111
homework.expression.ExpParser.ExpParser(String)323
homework.expression.ExpParser.getItemAdd(String,boolean)569
homework.expression.ExpParser.getItemMul(String)435
homework.expression.ExpParser.matchParentheses()748
homework.expression.ExpParser.readOneItem(String)6912
homework.expression.ExpParser.spliter()111
homework.expression.ExpSimplify.ExpSimplify(Derivable)122
homework.expression.ExpSimplify.OnlyOneDiff(Derivable,Derivable)91726
homework.expression.ExpSimplify.getDiff(boolean,int,int,ItemMul,ItemMul)146
homework.expression.ExpSimplify.searchSimplify(ItemAdd)444
homework.expression.ExpSimplify.simplify()224
homework.polyitem.factor.Factor.Factor()111
homework.polyitem.factor.Factor.Factor(BigInteger)111
homework.polyitem.factor.Factor.getIndex()111
homework.polyitem.factor.Factor.setIndex(BigInteger)111
homework.polyitem.factor.Factor.updateIndex(BigInteger)111
homework.polyitem.factor.FactorExp.FactorExp()111
homework.polyitem.factor.FactorExp.FactorExp(ItemAdd)111
homework.polyitem.factor.FactorExp.FactorExp(ItemAdd,BigInteger)111
homework.polyitem.factor.FactorExp.clone()111
homework.polyitem.factor.FactorExp.derivate()445
homework.polyitem.factor.FactorExp.equals(Object)334
homework.polyitem.factor.FactorExp.equalsZero()111
homework.polyitem.factor.FactorExp.getExpression()111
homework.polyitem.factor.FactorExp.identity()111
homework.polyitem.factor.FactorExp.toString()144
homework.polyitem.factor.FactorFactory.readPower(Matcher)122
homework.polyitem.factor.FactorFactory.readTrian(Matcher)123
homework.polyitem.factor.FactorFactory.simplifyFunc(Derivable)245
homework.polyitem.factor.FuncConst.FuncConst()111
homework.polyitem.factor.FuncConst.FuncConst(BigInteger)111
homework.polyitem.factor.FuncConst.FuncConst(String)111
homework.polyitem.factor.FuncConst.clone()111
homework.polyitem.factor.FuncConst.derivate()111
homework.polyitem.factor.FuncConst.equals(Object)222
homework.polyitem.factor.FuncConst.equalsOne()111
homework.polyitem.factor.FuncConst.equalsZero()111
homework.polyitem.factor.FuncConst.getValue()111
homework.polyitem.factor.FuncConst.identity()111
homework.polyitem.factor.FuncConst.setValue(BigInteger)111
homework.polyitem.factor.FuncConst.toString()111
homework.polyitem.factor.FuncConst.updateValue(BigInteger)111
homework.polyitem.factor.FuncPower.FuncPower()111
homework.polyitem.factor.FuncPower.FuncPower(BigInteger)111
homework.polyitem.factor.FuncPower.clone()111
homework.polyitem.factor.FuncPower.derivate()112
homework.polyitem.factor.FuncPower.equals(Object)222
homework.polyitem.factor.FuncPower.equalsZero()111
homework.polyitem.factor.FuncPower.identity()111
homework.polyitem.factor.FuncPower.toString()112
homework.polyitem.factor.FuncTrian.FuncTrian()111
homework.polyitem.factor.FuncTrian.FuncTrian(boolean)111
homework.polyitem.factor.FuncTrian.FuncTrian(boolean,BigInteger)111
homework.polyitem.factor.FuncTrian.FuncTrian(boolean,ItemAdd)111
homework.polyitem.factor.FuncTrian.FuncTrian(boolean,ItemAdd,BigInteger)111
homework.polyitem.factor.FuncTrian.clone()122
homework.polyitem.factor.FuncTrian.derivate()446
homework.polyitem.factor.FuncTrian.equals(Object)648
homework.polyitem.factor.FuncTrian.equalsZero()334
homework.polyitem.factor.FuncTrian.identity()145
homework.polyitem.factor.FuncTrian.toString()123
homework.polyitem.item.ItemAdd.ItemAdd()111
homework.polyitem.item.ItemAdd.ItemAdd(Derivable...)133
homework.polyitem.item.ItemAdd.ItemAdd(HashMap<String, Derivable>)111
homework.polyitem.item.ItemAdd.ItemAddPut(Derivable)144
homework.polyitem.item.ItemAdd.PutItemMul(ItemMul)155
homework.polyitem.item.ItemAdd.clone()122
homework.polyitem.item.ItemAdd.derivate()223
homework.polyitem.item.ItemAdd.equals(Object)323
homework.polyitem.item.ItemAdd.equalsZero()323
homework.polyitem.item.ItemAdd.getAdditem()111
homework.polyitem.item.ItemAdd.getOnlyOne()424
homework.polyitem.item.ItemAdd.identity()111
homework.polyitem.item.ItemAdd.onlyContainOne()111
homework.polyitem.item.ItemAdd.toString()657
homework.polyitem.item.ItemMul.ItemMul()111
homework.polyitem.item.ItemMul.ItemMul(Derivable...)333
homework.polyitem.item.ItemMul.ItemMulPut(Derivable)356
homework.polyitem.item.ItemMul.PutFactor(Factor)61010
homework.polyitem.item.ItemMul.PutItemMul(ItemMul)122
homework.polyitem.item.ItemMul.clone()122
homework.polyitem.item.ItemMul.derivate()627
homework.polyitem.item.ItemMul.equals(Object)323
homework.polyitem.item.ItemMul.equalsZero()323
homework.polyitem.item.ItemMul.getCoeff()222
homework.polyitem.item.ItemMul.getMutiitem()111
homework.polyitem.item.ItemMul.getOneExp()525
homework.polyitem.item.ItemMul.identity()234
homework.polyitem.item.ItemMul.negateCoeff(boolean)122
homework.polyitem.item.ItemMul.onlyOneExp()337
homework.polyitem.item.ItemMul.onlyOneFactor()437
homework.polyitem.item.ItemMul.removeItem(Derivable)122
homework.polyitem.item.ItemMul.setCoeffOne()111
homework.polyitem.item.ItemMul.toString()177
homework.polyitem.item.ItemMul.updateCoeff(BigInteger)111

MetricReloaded分析程序复杂度更加病态了,大量方法的结构化程度存在问题,并且集中在与化简有关的方法中。表达式解析中的readOneItem()方法三项均超标,虽然我已经进行了采用了一部分的工厂模式、将正则表达式存入单独的类中以被调用的解耦合的努力。

Package复杂度:

Packagev(G)avgv(G)tot
homework22
homework.expression6.68127
homework.polyitem.factor1.8693
homework.polyitem.item3.38115

通过分包,将几大功能区分开(不过这次看到20%的性能分,增加了暴力搜索提取公因式的简化方法,还是放在Exp处理中,所以耦合度也有点高)。

② 程序Bug分析

“OO中最惨的,不是被同屋hack了50个同质Bug,而是在截止提交的下一秒意识到了自己的Bug”

——沃茨基·硕德

在第三次作业中,我为了简化做了不少工作,也用随机数据自动测试程序做了大量的检验。然而就在周六晚上截止提交后的几秒,我突然意识到我的输出是有问题的——对于表达式因子,我将其设置为继承了Factor类,同样拥有指数Index属性,在输出时,采用的是和幂函数、三角函数一样的输出方式——“`(exp)**index`”,但在输入时表达式因子是不允许有指数的,因此属于WF。但对于Sympy,表达式的格式限定很宽松,通过计算并不能找到错误。

那能咋办……周六晚上看番转移注意力,周日在互测中尽可能挽回损失……最后发现强测没有出现这种输出,但互测时被同屋两名Servent发现了这个Bug。好在修复工作也比较简单,实在是侥幸。

③ 互测采用策略

在这次互测依然是采用对拍(对军宝具)+手动构造样例(我自己构造了可能会TLE的样例,也分享到了群里)+Python生成随机数据自动评测的方式。

在这次对Python生成随机数据做了优化,比如Caster在指数为0时会出现各种吊诡的错误,然而互测限定指数>0,于是我将对Caster的随机数据设为指数始终>0,其他成员照旧,避免了反复查看不能hack的Bug带来的失落感。

最后稍微吐槽一下“不优化就不会Die”,在这次互测中,房间内8名成员,4名优化输出4名不优化,最后出Bug的都是那4名优化输出的……其中Berserker还会在表达式嵌套过多时陷入死循环,截止至本文发稿时已被修好。

④ 对象创建模式

在这次的作业中终于用到了工厂模式!虽然原因是表达式解析方法太长,不得不将生成项的代码独立开来。不过我也只用工厂模式处理了幂函数、不含嵌套因子的三角函数、常数,含嵌套因子的三角函数的生成与我的状态机密切相关,难以抽象出来,这也是我的架构中的不足。

心得体会

在寒假的时候,我曾看到知乎上@HansBug学长关于OO课程改革的回答,而通过这一个多月的实际体验,OO给我的感受还是很赞的,通过互测阅读同学代码、与舍友们的交流(感谢@VOIDMalkuth!!!)、水讨论区以及学习理论课,收获很大。

第一单元也只是从面向过程到面向对象的过渡篇章,通过这次反思,我也发现了过去几次代码中自己结构较为不合理的部分,希望能够在接下来多线程等知识的学习中进一步改善。

note

这是一篇从Hexo迁移的文章,创建于2020-03-20 04:53:41