• OO第一单元总结


    OO第一单元总结

    第一次作业

    思路

    1. 将输入标准化,即去掉空白字符、将++替换为+等。
    2. 按照正则表达式提取出每一项。
    3. 以每一项的指数为key,系数为value,建立一个HashMap<BigInteger, BigInteger>。而每找到一个新的项a*x**b时,就在hashmap中寻找是否存在一个项与之指数相同,若找到则合并,若未找到则直接把这个新的项插入hashmap。
    4. 之后求导、输出即可。输出若系数为1或-1或0、指数为1或0,则可化简。

    UML

    LineCount

    Metrics

    metircs

      这还是我第一次接触这样的代码复杂度的分析工具,所以特地去百度了一下。这些ev(G)就是我的代码的圈复杂度、基本圈复杂度、环路复杂度,大概就是这些数值越高,说明代码的流程图越复杂、岔路什么的就越多,覆盖测试就更困难,当然也就更容易出bug。这都这么高了,说明逻辑结构上还是有很大的改进空间——事实上只需要对项实现toString()方法,输出系数指数就行,我写的输出函数,明显是太过复杂了。

    Bugs

      这第一次作业我就在互测环节被狠狠hack了,我们房间被hack数的三分之二都在我身上(悲)。我的错误在于,我在把项加入hashmap的时候偷懒,本想说一个项系数为0的时候就不要加入hashmap了,但是当两项合并后结果的系数若为0,则这个合并的结果不会加入hashmap,原来的项也没有从hashmap里出来,于是出了大问题。而这么大的问题居然在自己测试的时候没有发现,到底是为什么呢?需要反思。

    第二次作业

    思路

    1. 依照参考书的形式化表述,逐层构建正则表达式(大正则),最终只需检查输入的字符串是否符合表达式的正则形式即可,不必过于纠结局部。
    2. 确定格式正确后进行化简,基本思路同第一次作业。与第一次不同的是,我这次额外把+x-x+sin(x)等都换成+1*x**1-1*x**1+1*sin(x)**1的形式,确保每一项都是k * x**a * sin(x)**b * cos(x)**c的没有任何化简的形式,方便后续统一处理。至于优化什么的......没有金刚钻别揽瓷器活吧(心情复杂)。
    3. 建立HashMap<Key, BigInteger>,其中Key是每个项中x、sin(x)、cos(x)系数的三元组<a, b, c>,value则是这个项的系数k。每有一个新的项,若其能与HashMap中的项合并则合并,否则直接添加到HashMap中。
    4. 遍历各个项,求出导数并输出即可。

    UML

    UML2

    LineCount

    Linecnt2

    Metrics

    metrics2

      冷静分析一下这次代码的复杂度——

    • compareTo()是比较两个Key是否相等,因为一个Key包含着三个元素,所以使用if语句的逻辑就比较复杂,复杂度似乎不能再降低。
    • getTerm()是在输出答案字符串时,由HashMap中的键值生成字符串并化简(如把1*x变成x),由于可能存在多种化简的情况,就势必有很多if语句,似乎也不能降低复杂度。
    • parsePoly()是我本次作业中写的最迷糊、最混乱的一部分,甚至用了奇技淫巧才使得整个方法的行数小于60。大致就是先判断字符串合不合法,在逐个取出项并把这些项放到HashMap中。这很显然就是先后的三个动作嘛,完全可以拆分到三个函数中去,从而降低复杂度。所幸这部分和第一次作业整体结构差不多,才没出大bug。

    Bugs

      这次作业其实大体上和第一次作业差不多,所以万幸,在强测和互测中没有出现bug。

    第三次作业

    思路

    1. 一看到这次作业,我整个人是崩溃的,因子套因子这谁顶得住,何况我之前的代码完全没有考虑到函数嵌套的可能性,这下子绝对没办法向后兼容了,迫于无奈我只能——重构。
    2. 首先对于判断输入的合法性,由于嵌套结构的存在,单纯使用正则表达式已经不可行,只能先判断外层的合法性,再判断内层的合法性。而多项式Poly和因子Factor的判断方法不尽相同,同时Factor中可能包含Poly,Poly中也可能包含Factor,只能对这两个分别写两个函数再互相调用。比如对于输入cos((2*x**2 +- 3 *-x)) + x**2,其中((2*x**2 +- 3 *-x))是一个因子F,则把外层式子替换为cos(F) + x**2, 判断外层是否符合一个多项式的格式要求,再对内层判断其是否符合因子的格式要求。内层(2*x**2 +- 3 *-x),去掉括号后为2*x**2 +- 3 *-x,再判断其是否为Poly。如此层层递归才能得到结果。当然也有大佬用的是表达式树的方法,但是我不知道这样的方法如何处理好几个连续的运算符如--+2 * -x +- sin(x)(难道是把它们算作一个运算符?),所以没有采用。
    3. 确定输入合法后,将其进行化简,方法同第二次作业。
    4. 遍历整个表达式,将各项提取出来,项和项之间用加减法原则求导。项内部可能是因子相乘或单个因子,应用对应的求导法则即可。注意求导也是递归的过程,所以我采用的是将三角函数和幂函数统一设置为多项式Poly 的子类并实现返回值为Poly的求导方法。
    5. 至于化简,我不配,我真的不配.......
    6. 实际上这个思路相当混乱,还请大佬轻喷。

    UML

    UML3

    这结构实在是,太过混乱。

    LineCount

    LineCount3

    Metrics

    Metrics3

    红一大片如旭日东升。复杂度如此之高,只能说明自己的思路和代码结构过于混乱。

    Bugs

      这次的Bug倒不是出自代码问题(惊了这么丑的代码居然没出问题),而是出自思路问题。在最初的思路中,由于完全放弃了任何形式的优化,我就想,只要在表达式中__遇到第一个能把表达式分成前后两项的运算符号(即加减号),那么就可以把表达式分成前后两部分,只需对前后两部分应用求导法则就可以__。例如对2*x**2*sin((-x)) + cos((x**-1)) - x,找到的第一个加号就能把表达式分成2*x**2*sin((-x))cos((x**-1)) - x两项,再对两项分别求导之后加起来即可。前面的2*x**2*sin((-x))已经是个最小的项,就应用乘法法则和/或嵌套法则,后面的cos((x**-1)) - x还是若干个项相加减的形式,仍需递归地寻找各个项。

      但是这样我完全忽略了一个问题——对于减号如x - 2*x + 3*x,用减号分成了x2*x + 3*x,前半部分导数为1,后半部分导数为5,结果得到1 - 5 = -4!这是因为我用减号分开后,把后半部分当成了一个整体,却忘记了后半部分应该变号!鉴于变号是在太复杂,于是干脆把多项式中的每个项找出来,分别求导后再加减到一起。

      这个问题我认为是出在了偷懒上,自以为是地找到第一个加减号就直接把多项式分成两半了。同时这个问题更是出在测试数据严重不全面的事实上。数据越是复杂,越是考验我构造测试数据的能力。而这个能力,说实话我还远远不具备。

    测试的策略

      无论是检验自己程序的正确性,还是在互测中找到别人代码中的bug,都需要一套完善的测试策略。

    • 我首先想到的是自造评测机。利用Python中的exrex第三方库,可以根据正则表达式随机生成一个符合正则表达式的测试数据,再利用Python的sympy第三方库实现对测试数据求导之后。之后只需向Python求出的导数和java求出的导数代入数值并比较结果是否相等即可。可以使用脚本或者Python程序实现此过程的自动化运行。
    • 第一次互测中,我实在不想看别人的代码,于是直接让评测机生成随机数据去测试别人的代码,hack中了就把这个数据交上去,属实敷衍。由于提交自己代码的时候评测机还没搭好,所以没有对自己的进行自动化评测,也就出了个bug。
    • 第二次互测前,我先用自动化评测把自己的测了挺久,觉得应该问题不大。互测中,我沿用上一次的策略,直接把别人的代码甩给评测机,发现测不出bug。于是手动造了几组自认为覆盖比较全面的数据发现还是测不出bug,于是放弃。
    • 第三次的测试数据由于存在着复杂嵌套,单纯用正则表达式是难以生成数据的(但是现在一想好像其实生成数据也不是那么难),加上时间不足、心态爆炸,我甚至没来得及搭建好一台评测机,在此立正挨打。然而互测时,我把自己测试时候的两个括号层层嵌套的极端数据交上去居然还hack成功了......

      关于同质bug我想说的:我第一次和第三次作业都被疯狂hack,结果发现都是同质bug。虽然摁住别人的一个点死命刀真的很爽,但是确实没啥必要,所以以后这样的事我也是少干些为好,省得挨骂(小声)。

    对象创建模式

      第一次作业在一定程度上存在着向后兼容的可能,然而想法还是过于简单,以至于只求完成当前任务。下周可能拓展什么功能之类的,完全没有考虑,这是个严重的问题。

      第三次作业中见到了参考书的提示,思考之后觉得建立表达式树还是有点困难,于是采用了自己的方法,于是万劫不复。鉴于常数的求导很简单于是没有单独建立类,现在看来这样的处理不规范。此外建立了PowerFunc、SineFunc、CosineFunc类,都继承自Poly类,在Poly类中实现getDeri()toString()方法,并在三个子类中重写。这里我的Poly类中还实现了其他操作,所以这样的处理也是不规范。

      实际上,从第二次作业开始,就可以使用工厂模式建立类——幂函数、正弦函数、余弦函数和常数,让他们都继承自一个函数抽象类,并实现求导接口。

    体会和反思

    • 构造数据的能力过于弱,要么是完全交给评测机,要么只是简单尝试手动构造数据,虽然能构造出一些极限数据,但是根本没有一套思维方法来保证自己测试数据的全面性。
    • 对于构造模式,特别是在第三次作业,明明指导书给了提示、自己也事先列了框架,但是偏偏在代码实现中就把这些现成的、规范的模式抛到脑后,应该还是使用不熟练的缘故,回去好好看看工厂模式啊、多态啊、接口之类的知识才行。明明最近学的就是这些,思维方式千万不能不掌握啊。
    • 关于心态:没什么可说的,第三次作业看到题目的时候我人直接傻了,再看到讨论区里大佬们讨论的风生水起而且讨论内容我还不太看得懂,直接就顶不住了啊。心态混乱之后的就是思路混乱,思路混乱之后的就是代码混乱,代码混乱又加剧心态混乱......虽说coding确实有乐趣但是也禁不住这么糟蹋啊。
    • 对于构思:以后的代码肯定一次比一次复杂,必须提前构思,甚至可以提前到发题之前。
    • 对于面向对象:感觉自己似乎仍然被困在C语言的思维框架里,必须必须走出来才行,多看看代码和网课吧!还应该有意识地多实用对象创建模式等。

    最后图一乐吧

    一乡二里共三夫子,不识四书五经六艺,竟敢教七八九子,十分大胆

    十天九夜写八java,测试七次六错五改,强测WA四三二点,一只菜鸡

    莺莺燕燕翠翠红红处处融融恰恰

    日日夜夜天天OO个个神神叨叨

    图画里,龙不吟虎不啸,小小书僮可笑可笑

    网站上,神不言佬不语,区区我等抄谁抄谁

  • 相关阅读:
    500内部服务器错误
    silverlight中实现页面传值
    每个程序员必备的10种工具
    ora-12514:TNS:listener does not currently know of service requested in connect descriptor
    利用VBA 宏实现vc6.0的自动添加注释和自动取消注释
    plsql developer (在8.04版本中试过可用)
    修改Oracle数据库字符集
    80端口被占用(端口检查)解决
    sql server分页
    解决问题 “You don't have permission to access /index.html on this server.”
  • 原文地址:https://www.cnblogs.com/Locksoyev/p/12521535.html
Copyright © 2020-2023  润新知