面向对象程序设计第一单元总结

OO第一单元博客

第一次作业

作业摘要

第一次作业需要完成的是简单多项式导函数的求解

作业思路

  • 第一次作业涉及到的求导类型比较单一,就涉及到幂函数的求导和常数求导

    \[f(x) = c,(c 为常数), f'(x) = 0 \]

\[f(x) = x^n(n \neq 0), f'(x) = n\cdot x ^ {n-1} \]

  • 第一次作业用两个类完成,当时不知道后续会增加什么功能,所以架构上并不严谨。总体思路是这样的,对于每一个输入的字符串,对其进行正则表达式匹配,正则表达式按照加减号将表达式分成了很多个项,而每一个项又是由一串的因子相乘得到的。在主类里完成了正则表达式匹配和结果输出,此外还有一个Item类,表达的是项类,包含了项的属性,指数exp和系数coe,并且在Item项中实现了求导,将求导后的幂函数的系数和指数再次记录下来,分别为derivedCoe和derivedExp

下面是第一次作业的UML图

下面是Class metrics信息, 可以发现主类实现的功能过多,与低耦合的理念相悖

输出优化

  • 合并同类项
    由于求导是对每一项做的求导,那么就有可能在求导之后获得指数相同的项,完美需要做的就是将指数相同的项的系数相加。此时我们想到Java中的一种数据结构HashMap,我们将其Key设为指数,Value设为系数,将指数相同的系数相加就可以完成同类项合并
  • 排序优化
    我们在输出的时候,如果第一个项的系数为正的话,那么我们输出的时候不需要输出正号,但如果是负数的话我们最后的输出长度会增长,所以对项按系数排序,按系数由大到小输出。
  • 特殊点优化
    由于在此题中函数的次幂是用符号**表示,所以有一些项如:x**2 用指数幂的形式表示,字符长度比用乘积的方式表达(x*x)更长,所以可以再次进行优化

BUG 分析

自己的bug

  • 第一次作业时在优化的时候只是简单的将系数为零的项去除掉,没有考虑清楚只有0这一项的情况

他人的bug

  • 不能处理\(x-x\)类型的数据(没有输出)
  • 不能处理指数/系数过大的情况,没有使用BigInteger
  • 没有考虑到多个符号的情况如:(\(---1*x**3\)),我的处理方法是对项先做除符号处理,即遇到一个符号,项的系数乘以\(-1\)

第二次作业

作业摘要

第二次作业需要完成的任务为包含简单幂函数和简单正余弦函数的导函数的求解

在第一次作业的基础上增加了正弦函数和余弦函数(只包含\(sin(x), cos(x)\)),此外还增加了括号嵌套, 如\((-x ** 3 + x * (x ** 2 + sin(x)))\)

作业思路

  • 第二次作业相较第一次作业增加了两个大点,正余弦函数和括号嵌套,而后者又是这次作业的关键。

  • 第一次作业的架构过于简单,可拓展性太低,所以第二次对第一次作业进行了重构。此外在阅读学长博客的时候,发现第三次作业也许还要实现三角函数内部嵌套,所以又将三角嵌套也考虑进入了作业架构。

  • 第一次作业的正则匹配思路是将表达式看成多个项相加,而每一个项又是由因子向乘所构成的,第二次作业想沿用第一次作业的难点就在与括号嵌套,我们如何处理括号呢?首先我们将因子扩充,有正弦因子,余弦因子,常数因子,幂函数因子和表达式因子。其中比较特殊的是正弦因子和余弦因子,因为其内部组成除了指数之外还需要包含sin(), cos()括号里面的部分,考虑到第三次作业会对括号里部分进行嵌套,我们将括号里面的部分设置为表达式类,而且我们发现这些因子有一些共同的需要实现的功能,如:求导diff(), 转化为字符串toSting(), 克隆clone()等。我们就在建立一个抽象类Factor来包含这些因子所共同的方法。有了因子,就可以组件成项,所以我们在建立一个项类Item,里面包含一个因子数组ArrayList<Factor> 表示这一项所表示的因子。再往上,我们发现一个个的项相加或相减构成了表达式,所以再构建一个类Expression,内部有一个项数组ArrayList<Item>表示表达式所蕴含的项。由此,本次作业的架构就基本实现,再加上最后对于求导结果的优化ResultBeautify类就组成了这次作业的所有类。

  • 架构完成之后就是如何实现具体的功能,就比如括号内的表达式如何求导。这里利用到的是递归求导的思维,表达式求导是由每一项求导的结果相加,而项求导是利用了乘法法则,对项中的每一个因子分别求导,而各个因子有着各自的求导法则,但由于我们构建了表达式因子,所以可以将表达式进行递归的求导。

  • 现在还有一个问题就是如何将表达式分离成单独的每一项,在第一次作业中,我们利用正则表达式,按照形式化表达的描述将项分离了出来。但是现在由于项可能是由表达式组成的,而正则表达式不擅长处理递归的匹配。所以我们首先将一个表达式的最外层括号的位置找到。将最外层括号中间的内容取出来,按照顺序放到表达式数组中(以便后续取用),然后将我们最外层括号中间的内容删除,只留下两边的括号。现在我们在对其进行项匹配,由于繁杂的嵌套没有了,我们可以很容易的将每一项匹配完成,然后对每一项,按照乘号将其分离为因子,若因子是sin(), cos(), ()类型的话说明其内部含有表达式,我们将之前得到的表达式数组中的元素取出,并赋给每一个因子。至此,我们就完成了第二次作业的架构。

下面是第二次作业的UML图

下面是第二次作业的class metrics分析

优化思路

由于第二次作业并没有三角函数的内部嵌套,所以我们仍旧可以进行一些优化。

  • 拆括号

    注意到,这次作业虽然有着众多的括号嵌套,但最后我们将括号展开后可以发现所有的项都符合这样的一个式子\(a\cdot x ^{b}\cdot sin(x)^{c} \cdot cos(x)^{d}\) ,二这样的式子可比一堆括号嵌套在一起来的短。

    此外对于另外一类:形如\((1-x)(1+x+x^2+x^3+x^4)\),如果我们不拆括号,而是将其看做两个表达式乘积的形式进行求导,的出来的结果将会很长,二实际上花间后其就是\(1-x^5\)而已,对这样形式式子求导得到的结果势必比之前方法得到的式子短太多。结果也验证了,第二次作业强测中如果采取不拆括号的话将会损失非常多的性能分。

  • 三角恒等式

    最常见的\(sin(x)^2 + cos(x)^2=1\), 可以验证,对于第二次作业中形似\(a\cdot x ^{b}\cdot sin(x)^{c} \cdot cos(x)^{d}\)的式子,只要满足一定的条件,就能将其合并,如两个式子\(a_1\cdot x ^{b_1}\cdot sin(x)^{c_1} \cdot cos(x)^{d_1}\)\(a_2\cdot x ^{b_2}\cdot sin(x)^{c_2} \cdot cos(x)^{d_2}\),能用上述式子进行合并的充要条件是
    \(\left\{\begin{aligned} a_1=b_1 \\ a_2=b_2 \\ c_1-c_2=2 \\ d_2-d_1=2 \end{aligned}\right.\)\(\left\{\begin{aligned} a_1=b_1 \\ a_2=b_2 \\ c_2-c_1=2 \\ d_1-d_2=2 \end{aligned}\right.\)

由此可以将二式合并为\(a_1\cdot x ^{b_1}\cdot sin(x)^{c_1-2} \cdot cos(x)^{d_1-2}\)

  • 特殊优化

    • 对零的优化

      对求导后为零的项(系数为零),如果不是只有一项0的话,可以将其删去

    • 对一的优化

      系数为1的话可以不输出系数,系数为-1且为项的第一个因子的话可以将-1省略为输出-号
      若指数为1的话, 可以省略

    • 同第一次可以将\(x**2\)优化为\(x*x\)

BUG 分析

自己的bug

  • 余弦函数\(cos(x)\)求导的时候忽略了形式话表达的描述,直接输出了\(-sin(x)\),在很多情况下是不对的,在弱侧和中测中没有体现,也没有意识到,在利用对拍器对拍的时候,由于没有进行格式检查,只是进行了正确性判断,也一直没有将这一个bug找出来,导致这一个bug被同质性hack了无数次😢

他人的bug

  • 不能处理括号内多个符号问题如:\((72*+56+90))--(++61)*(+++46)\)
  • 不能处理多层嵌套问题

第三次作业

作业摘要

第三次作业需要完成的任务为包含简单幂函数和简单正余弦函数及其嵌套组合函数的导函数的求解。此外还需要完成表达式正确性判断。

作业思路

  • 由于已经在第二次就完成了表达式嵌套的工作,这次作业的重头戏在表达式正确性判断。

  • 一开始我列举了很多的错误类型,如:出现了非法字符,括号不匹配,表达式次幂,常数次幂\(\cdots \cdots\) 但是后来发现,如果一个一个实现每一个错误类型的话是几乎不可能的,因为可能的错误类型太多了,如果光靠人脑列举的话很可能就会有疏忽,所以思路转化,反过来想:只要知道什么是正确的就行了。而根据指导书给出的内容,设定的形式化表述完美的完成了这一点。只需要按照形式化表述将正则表达式写出来,就能完成正确性匹配了。此外对于不能匹配到的地方,我自定义了一个异常类,如果匹配不到的话就将异常抛出,在主类中捕获到异常,并输出。

  • 表达式 \(\rightarrow\) 空白项 [加减 空白项] 项 空白项 | 表达式 加减 空白项 项 空白项
    \(\rightarrow\) [加减 空白项] 因子 | 项 空白项 * 空白项 因子
    因子 \(\rightarrow\) 变量因子 | 常数因子 | 表达式因子
    变量因子 \(\rightarrow\) 幂函数 | 三角函数
    常数因子 \(\rightarrow\) 带符号的整数
    表达式因子 \(\rightarrow\) '(' 表达式 ')'
    三角函数 \(\rightarrow\) sin 空白项 '(' 空白项 因子 空白项 ')' [空白项 指数] | cos 空白项 '(' 空白项 因子 空白项 ')' [空白项 指数]
    幂函数 \(\rightarrow\) x [空白项 指数]
    指数 \(\rightarrow\) ** 空白项 带符号的整数
    带符号的整数 \(\rightarrow\) [加减] 允许前导零的整数
    允许前导零的整数 \(\rightarrow\) (0|1|2|…|9){0|1|2|…|9}
    空白字符 \(\rightarrow\) (空格) | \t(水平制表符)
    空白项 \(\rightarrow\) {空白字符}
    加减 \(\rightarrow\) + | -

第三次作业UML图

第三次作业Class Metrics

BUG分析

自己的bug

  • 此次作业没有bug

他人的bug

  • 求导时对嵌套处理不完善,不能处理嵌套后的项的系数,导致最终结果是真正结果的-1倍
  • 第三次作业规定指数绝对值超过50非法,但是有些同学没有分清,将等于50的情况也计入非法。

心得体会

  • 第一单元的作业加深了我对于面向对象的理解,同时debug的能力也有了一定的提升,特别在是第二次作业进行重构的时候。
  • 对程序架构有了一定的了解,对高内聚,低耦合有了进一步的了解
  • 进一步熟悉java语言的使用,了解了更多java特性。
posted @ 2021-03-26 16:36  QuantumBolt  阅读(72)  评论(0编辑  收藏  举报