BUAA-OO-Unit1
第一次作业
第一次作业较为简单总共有13个类,主要考查的是表达式的解析和简单的表达式展开,每个类的方法和属性如下图
每个方法的复杂度如下,其中设计复杂度Parser.parseFactor()最高,因为要判断Factor的类型所以有很多if-else,最后设计的复杂度就上去了。
| method | CogC | ev(G) | iv(G) | v(G) |
|---|---|---|---|---|
| Processor.adjustSign(String) | 17.0 | 1.0 | 6.0 | 9.0 |
| Parser.parseFactor() | 16.0 | 7.0 | 9.0 | 11.0 |
| Lexer.parseTokens() | 9.0 | 3.0 | 4.0 | 12.0 |
| Mono.toString() | 6.0 | 3.0 | 5.0 | 6.0 |
| Parser.parseExpr() | 6.0 | 1.0 | 5.0 | 6.0 |
| Poly.rebuildMonoList() | 6.0 | 3.0 | 5.0 | 5.0 |
| Poly.mulPoly(Poly) | 5.0 | 2.0 | 4.0 | 5.0 |
| Poly.toString() | 4.0 | 3.0 | 3.0 | 5.0 |
| Parser.parseTerm(int) | 3.0 | 1.0 | 4.0 | 4.0 |
| Poly.powPoly(int) | 3.0 | 1.0 | 3.0 | 3.0 |
| Lexer.peekToken(int) | 2.0 | 2.0 | 2.0 | 3.0 |
| Lexer.peekTokenType(int) | 2.0 | 2.0 | 2.0 | 3.0 |
| Parser.parseExponent() | 2.0 | 2.0 | 3.0 | 3.0 |
| Poly.addPoly(Poly) | 2.0 | 1.0 | 3.0 | 3.0 |
| Poly.putTerm(int, BigInteger) | 2.0 | 1.0 | 2.0 | 2.0 |
| Term.toPoly() | 2.0 | 1.0 | 3.0 | 3.0 |
| Expr.toPoly() | 1.0 | 1.0 | 2.0 | 2.0 |
| Lexer.nextPos() | 1.0 | 1.0 | 1.0 | 2.0 |
| MainClass.main(String[]) | 1.0 | 2.0 | 1.0 | 2.0 |
| Poly.negate() | 1.0 | 1.0 | 2.0 | 2.0 |
| PowerFactor.toPoly() | 1.0 | 2.0 | 1.0 | 2.0 |
| Processor.Processor(String) | 1.0 | 1.0 | 1.0 | 2.0 |
| Expr.Expr() | 0.0 | 1.0 | 1.0 | 1.0 |
| Expr.addTerm(Term) | 0.0 | 1.0 | 1.0 | 1.0 |
| ExprFactor.ExprFactor(Expr) | 0.0 | 1.0 | 1.0 | 1.0 |
| ExprFactor.toPoly() | 0.0 | 1.0 | 1.0 | 1.0 |
| Factor.getExp() | 0.0 | 1.0 | 1.0 | 1.0 |
| Factor.setExp(int) | 0.0 | 1.0 | 1.0 | 1.0 |
| Lexer.Lexer(String) | 0.0 | 1.0 | 1.0 | 1.0 |
| Lexer.getPos() | 0.0 | 1.0 | 1.0 | 1.0 |
| Lexer.getToken() | 0.0 | 1.0 | 1.0 | 1.0 |
| Lexer.getTokenType() | 0.0 | 1.0 | 1.0 | 1.0 |
| Mono.Mono(BigInteger, int) | 0.0 | 1.0 | 1.0 | 1.0 |
| Mono.getCoe() | 0.0 | 1.0 | 1.0 | 1.0 |
| Mono.getExp() | 0.0 | 1.0 | 1.0 | 1.0 |
| NumFactor.NumFactor(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
| NumFactor.toPoly() | 0.0 | 1.0 | 1.0 | 1.0 |
| Parser.Parser(Lexer) | 0.0 | 1.0 | 1.0 | 1.0 |
| Poly.Poly() | 0.0 | 1.0 | 1.0 | 1.0 |
| Poly.addToTerm(int, BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
| Poly.formMono(BigInteger, int) | 0.0 | 1.0 | 1.0 | 1.0 |
| Poly.getMonoMap() | 0.0 | 1.0 | 1.0 | 1.0 |
| Poly.one() | 0.0 | 1.0 | 1.0 | 1.0 |
| PowerFactor.PowerFactor(String) | 0.0 | 1.0 | 1.0 | 1.0 |
| Processor.delBlank(String) | 0.0 | 1.0 | 1.0 | 1.0 |
| Processor.newInput() | 0.0 | 1.0 | 1.0 | 1.0 |
| Term.Term() | 0.0 | 1.0 | 1.0 | 1.0 |
| Term.addFactor(Factor) | 0.0 | 1.0 | 1.0 | 1.0 |
| Term.getSign() | 0.0 | 1.0 | 1.0 | 1.0 |
| Term.setSign(int) | 0.0 | 1.0 | 1.0 | 1.0 |
| compare(Mono, Mono) | 0.0 | |||
| Total | 93.0 | 70.0 | 99.0 | 123.0 |
| Average | 1.8235294117647058 | 1.4 | 1.98 | 2.46 |
第一次作业在强测和互测都没有出现bug,也没有发现别人的bug。
第一次的性能分我并没有拿满,原因是我没有把正项提前的处理,导致如果最后有负项在第一项长度会比优化过的多一个字符。
写完第一次作业我就一直不是很确定我代码到底能不能过强测,我自认为可能一部分来自AI的使用,一部分是来自测试的缺失。这种感觉一直在后面的两次作业都有,我觉得在第二单元要在写完代码之后多测试确保代码的基本功能,而并非是胡乱测同学给的支零破碎的数据点。
最后说一下第一次作业的AI心得(既然不影响得分那我就直说了),AI目前来说已经完全具备完成OO作业的能力了,我觉得应该拥抱AI改进自己使用AI的能力。我觉得首要要解决的是上下文爆炸的问题,当你和AI完成多次对话的时候AI会逐渐忘记原来的题目和你的初始代码,导致最后出现一些不合题意的修改,这时候可以采用尽量不多轮对话的策略,就是把题目代码发给AI之后就通过不断修改第二次对话来控制上下文长度。
第二次作业
首先是代码的复杂度,本质复杂度最高的依旧是Parser.parseFactor()没什么可说的,那么多的Factor类型要判断复杂度当然高。其实我并没有发现复杂度它到底在分析什么,因为这种东西在你架构的时候是完全没有的,它只是对你代码的一个概括。
| method | CogC | ev(G) | iv(G) | v(G) |
|---|---|---|---|---|
| compare(Mono, Mono) | 0.0 | |||
| EvalContext.EvalContext(Poly, FunctionDef) | 0.0 | 1.0 | 1.0 | 1.0 |
| EvalContext.defaultContext(FunctionDef) | 0.0 | 1.0 | 1.0 | 1.0 |
| EvalContext.getFunctionDef() | 0.0 | 1.0 | 1.0 | 1.0 |
| EvalContext.getVariablePoly() | 0.0 | 1.0 | 1.0 | 1.0 |
| ExpFactor.ExpFactor(Factor) | 0.0 | 1.0 | 1.0 | 1.0 |
| Expr.Expr() | 0.0 | 1.0 | 1.0 | 1.0 |
| Expr.addTerm(Term) | 0.0 | 1.0 | 1.0 | 1.0 |
| ExprFactor.ExprFactor(Expr) | 0.0 | 1.0 | 1.0 | 1.0 |
| ExprFactor.toPoly(EvalContext) | 0.0 | 1.0 | 1.0 | 1.0 |
| Factor.getExp() | 0.0 | 1.0 | 1.0 | 1.0 |
| Factor.setExp(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
| FuncFactor.FuncFactor(Factor) | 0.0 | 1.0 | 1.0 | 1.0 |
| FuncFactor.toPoly(EvalContext) | 1.0 | 2.0 | 1.0 | 2.0 |
| FunctionDef.FunctionDef(Expr) | 0.0 | 1.0 | 1.0 | 1.0 |
| FunctionDef.apply(Poly, FunctionDef) | 0.0 | 1.0 | 1.0 | 1.0 |
| Lexer.Lexer(String) | 0.0 | 1.0 | 1.0 | 1.0 |
| Lexer.addOneToken(char) | 1.0 | 1.0 | 1.0 | 14.0 |
| Lexer.addToken(String, TokenType) | 0.0 | 1.0 | 1.0 | 1.0 |
| Lexer.getPos() | 0.0 | 1.0 | 1.0 | 1.0 |
| Lexer.getToken() | 0.0 | 1.0 | 1.0 | 1.0 |
| Lexer.getTokenType() | 0.0 | 1.0 | 1.0 | 1.0 |
| Lexer.nextPos() | 1.0 | 1.0 | 1.0 | 2.0 |
| Lexer.startsWithAt(String, int) | 0.0 | 1.0 | 1.0 | 1.0 |
| Mono.Mono(BigInteger, BigInteger, Poly) | 0.0 | 1.0 | 1.0 | 1.0 |
| Mono.expFactorString() | 1.0 | 2.0 | 1.0 | 2.0 |
| Mono.getCoe() | 0.0 | 1.0 | 1.0 | 1.0 |
| Mono.getExpPoly() | 0.0 | 1.0 | 1.0 | 1.0 |
| Mono.getSortExpSignature() | 1.0 | 2.0 | 1.0 | 2.0 |
| Mono.getXExp() | 0.0 | 1.0 | 1.0 | 1.0 |
| Mono.toString() | 2.0 | 2.0 | 1.0 | 3.0 |
| Mono.xFactorString() | 2.0 | 3.0 | 1.0 | 3.0 |
| NumFactor.NumFactor(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
| NumFactor.toPoly(EvalContext) | 0.0 | 1.0 | 1.0 | 1.0 |
| Parser.Parser(Lexer) | 0.0 | 1.0 | 1.0 | 1.0 |
| Parser.parseFuncFactor() | 0.0 | 1.0 | 1.0 | 1.0 |
| Parser.parseFunctionDefinition() | 0.0 | 1.0 | 1.0 | 1.0 |
| Parser.parseNumber() | 0.0 | 1.0 | 1.0 | 1.0 |
| Parser.parseSelectFactor() | 0.0 | 1.0 | 1.0 | 1.0 |
| Parser.parseSignedNumber() | 1.0 | 1.0 | 1.0 | 2.0 |
| Term.Term() | 0.0 | 1.0 | 1.0 | 1.0 |
| Term.addFactor(Factor) | 0.0 | 1.0 | 1.0 | 1.0 |
| Term.getSign() | 0.0 | 1.0 | 1.0 | 1.0 |
| Term.setSign(int) | 0.0 | 1.0 | 1.0 | 1.0 |
| Expr.toPoly(EvalContext) | 1.0 | 1.0 | 2.0 | 2.0 |
| Lexer.peekToken(int) | 2.0 | 2.0 | 2.0 | 3.0 |
| Lexer.peekTokenType(int) | 2.0 | 2.0 | 2.0 | 3.0 |
| MainClass.main(String[]) | 5.0 | 5.0 | 2.0 | 5.0 |
| Mono.hasExpFactor() | 1.0 | 1.0 | 2.0 | 2.0 |
| Parser.expect(TokenType) | 1.0 | 2.0 | 2.0 | 2.0 |
| Parser.parseExpFactor() | 1.0 | 1.0 | 2.0 | 2.0 |
| Parser.parseExponent() | 1.0 | 1.0 | 2.0 | 2.0 |
| Parser.parseExprFactor() | 1.0 | 1.0 | 2.0 | 2.0 |
| Parser.parsePowerFactor() | 1.0 | 1.0 | 2.0 | 2.0 |
| Poly.MonoKey.MonoKey(BigInteger, Poly) | 3.0 | 1.0 | 2.0 | 3.0 |
| Poly.MonoKey.equals(Object) | 3.0 | 3.0 | 2.0 | 4.0 |
| Poly.addToMap(HashMap, MonoKey, BigInteger) | 3.0 | 2.0 | 2.0 | 3.0 |
| Poly.isSingleConstant() | 2.0 | 2.0 | 2.0 | 3.0 |
| Poly.putTerm(MonoKey, BigInteger) | 2.0 | 1.0 | 2.0 | 2.0 |
| SelectFactor.toPoly(EvalContext) | 1.0 | 2.0 | 2.0 | 2.0 |
| ExpFactor.toPoly(EvalContext) | 2.0 | 3.0 | 3.0 | 3.0 |
| Term.toPoly(EvalContext) | 2.0 | 1.0 | 3.0 | 3.0 |
| Parser.parseTerm(int) | 3.0 | 1.0 | 4.0 | 4.0 |
| Poly.MonoKey.multiply(MonoKey) | 5.0 | 1.0 | 4.0 | 5.0 |
| Poly.mulPoly(Poly) | 5.0 | 2.0 | 4.0 | 5.0 |
| Poly.powPoly(BigInteger) | 5.0 | 1.0 | 4.0 | 4.0 |
| Parser.parseExpr() | 6.0 | 1.0 | 5.0 | 6.0 |
| Poly.buildMonoList() | 6.0 | 3.0 | 5.0 | 5.0 |
| Lexer.parseTokens() | 11.0 | 5.0 | 6.0 | 7.0 |
| Processor.adjustSign(String) | 17.0 | 1.0 | 6.0 | 9.0 |
| Mono.absBodyString() | 9.0 | 2.0 | 7.0 | 8.0 |
| Parser.parseFactor() | 8.0 | 8.0 | 8.0 | 9.0 |
| Total | 159.0 | 150.0 | 182.0 | 239.0 |
| Average | 1.59 | 1.5151515151515151 | 1.8383838383838385 | 2.414141414141414 |
第二次作业要求实现指数函数因子exp,选择式因子,自定义函数,新增ExpFactor,SelectFactor,FuncFactor,EvalContext,FuncionDef类。架构相对第一次作业没有太大的变化,依旧是表达式三层Factor,Term,Expr,语法解析器Parser,词法解析器Lexer,单项式Mono,多项式Ploy。主要修改了原有的Poly和每个子Factor的toPoly()方法。
第二次作业在最后输出的时候提取了表达式含有的共同因数,这里并没有涉及到exp拆分的问题所以不需要特别的注意exp是否优化完加上括号。
第二次互测没有人hack到我,我只hack到了一个人。具体的方法是利用大模型,先看不同人的架构,找到架构逻辑较为混乱的一个,把他的代码发给AI,让AI找架构方面可能导致的问题(bushi),这个方法屡试不爽,总能找到一些小聪明导致的错误。
第二次作业同样的没有做太多的优化,主要是做作业的时机没有把握好,作业下发之后我往后拖了好久,这就导致我几乎没有多少时间去优化和测试。听说第二单元性能比较重要,所以要留足够多的时间来做优化和测试。
就目前来看优化主要是两方面的一个是算法本身的时间开销,另外就是java的内存分配管理和回收。第一种优化它的安全性是较高的,不会造成内存访问错误等问题。第二种优化涉及到缓存,静态变量所以优化的时候要小心一些内存访问的错误。
第三次作业
第三次作业主要是功能的拓展,架构变化相对于第二次较小。代码的总架构如下图,我省去了每个类的属性,保留了一些主要的方法。

第三次作业的复杂度分析和第二次第一次基本上保持一致,这里就不占篇幅了。
第三次作业强测我错了一个点,报错的原因是TLE。但其实如果不是TLE我还是会错,这就要归功于互测了。互测的时候我进了C房,我找了一个架构比较混乱的同学的代码发给了AI让它分析,结果分析出来递推表达式递推规则最后多个函数项的计算出了错。我构造了一个数据点去测全屋的人过测出来4个人都是错的,也包括我在内。这就很有意思了,一个bug掩盖了另一个bug,如果不是互测估计强测我要好找。
分析原因为什么我的代码性能较差?客观来说就是你架构不够好,那是什么原因导致了客观上的弱势呢?当然是主观上的轻视,我觉得虽然AI能够帮我把代码的基本框架写好但是我还是要去主动的修改代码,即使是再次喂给AI。
互测策略
- 黑盒测试:只通过通过构造数据点来测试
- 白盒测试:结合被测试者的代码有针对性地构造数据点
目前我主要是采用黑盒测试,第三次作业要求实现exp,针对exp可以重点测试exp的嵌套,exp内的符号处理,exp的输出合法性。比如exp((exp((-y)))), exp((-exp((x+y)))), exp(exp((2222*x+2222+567*x^2-567*x^5)))选择式因子可以针对判断表达式是否相等,中括号是否解析正确比如[(exp((x*2))==exp(x)^2)?0:1], [(exp(0)^0==0^0)?0:1]-1一般函数调用可以和exp结合构造复杂的计算考察性能。特殊函数调用可以针对递推规则是否正确解析因子,是否正确递推。求导因子可以针对exp(),看看链式法则,乘法法则是否正确。
其中有一些比较有代表性的数据
**TLE**
1
f(x)=exp(exp(exp(exp(exp(exp(x)^2)^2)^2)^2)^2)^2
0
f(f(f(f(x))))
**WA**
0
0
exp((-exp((-y))))
目前来看我的测试策略并不高效,每一个数据点都要人来想这种更适合白盒测试,黑盒测试需要自动化生成测试数据的程序,第二单元可以试着做一下。
优化
输出优化集中在exp()的优化,有两种方法,一种是寻找exp()内部表达式的每一项的因数,选择一个提取出来变成指数;另一种就是拆分exp(),因为有时候exp()内部表达式各项系数相差过大不适合统一提出比如(尤其要注意拆分成多项的时候不要忘了加括号)
0
0
exp((-exp((22222222*x+22222222+3453*x^3-6906*y))))
除了exp输出的优化,在计算过程中采用快速幂可以大大减少时间,还可以减少空间的开销,极力推荐
大模型使用相关
-
正确性:第一次作业让大模型架构递归下降,第三次作业大模型分析求导因子的实现,递推函数如何递归,如何解决拆分之后exp内部不再是因子的问题。
-
性能优化:第二次作业让大模型做提取
GCD的优化,第三次作业让大模型分析如何拆分exp。
除了作业使用大模型最多的地方就是互测的时候找bug了,把代码发给大模型就可以做白盒测试了。
学习心得
第一单元的难点主要在于架构,通过第一单元的学习我明白了好的架构是非常重要的,所以第一次作业一定要好好做。同时在管理多项式的时候需要用到HashMap等数据结构,我练习了有关数据结构不同方法get(),put()以及entry遍历HashMap的方法。
还有比较重要的一点就是如何平衡你代码运行的时间和结果性能之间的矛盾,我认为可以做一些最基本的优化,再复杂的优化对我而言已经超出能力范围了,而且容易导致运行超时,性价比较低。
未来方向
我认为现在大模型对实验教学产生了颠覆性的影响,考虑到OO代码的复杂性设计上机实验是不太行的(除非是上机考java语法)。沿用目前线下迭代的思路我认为可以考察一些背景比较新的设计,这样大模型就不会那么的容易就把代码给写出来。目前的理论教学我觉得已经足够了,线下研讨课我认为是一个非常好的机会,建议继续保留。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)