文章目录
OO第一单元总结
作者:Appleuiy 一只greenbird
前言
- 如果有幸,这篇博客在未来被某个学弟学妹看到了,并且你想要学习借鉴我的学习经验,建议你先快速浏览整篇博客,因为我的架构虽然我通过了前两次作业,但不能否认它是一个失败的架构,你可以参考一下,避免自己出现相同问题
转眼间4个周过去了,OO第一个单元也迎来了结束。先说一下结果吧:三次作业提交,前两次都非常顺利,被hack次数为0,第三次没有通过中测(留下了悔恨的泪水)。
从第一周java语言0基础到现在能够较为熟练的使用java编写程序,不得不说,这高强度的3个星期,给我带来了非常巨大的收获。
单元任务概述
第一单元的作业主要围绕着多项式,包括多项式的展开、化简、函数替换、求导等。
通过一步步的迭代,使得学生能够在期间慢慢理解到底什么是面向对象,同时在一次次难度的提高中,也让学生渐渐明白,一个良好的架构对一项需要不断迭代的工程有多么重要
具体实现
题目
- 这是第一次作业的描述,第二、三次会有新增的要求
给定带有括号、指数、参数有xyz的多项式,要求将其展开,并尽量化简,最后输出的等价式子越短,性能分越高
KaTeX parse error: Expected & or \\ or \cr or \end at position 106: …5929f3732a6.png#̲pic_center) ![在…
递归下降
展开、合并化简等多多项式的操作,离不开解析多项式,对于第一次作业,可以取巧使用正则表达式,但是正解还得是我们的递归下降
递归下降在我理解是一个文法分析算法,他能处理一些有层次、文法的语言,这里的递归、下降针对的其实就是文法定义中的递归定义。第一次作业文法如下,细细品味一下不难发现其中的**“递归、下降”**
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L4hET9EO-1679193567799)(C:\Users\ZP and ZNQ\AppData\Roaming\Typora\typora-user-images\image-20230316212325193.png)]
基本架构
- 后面的迭代都是在最初的架构上进行,所以最初的架构非常重要,但是在初次架构上,我并没有用长远的目光去给未来的迭代留下过多的可能,为我后面的失败埋下了伏笔。
第一次题目题意描述上非常简单,对于化简后的多项式,我们稍加分析,就能得到基本项(我将其命名为Basic,并将其创建为Basic类)的表现形式,如下:
B
a
s
i
c
=
t
∗
x
a
∗
y
b
∗
z
c
\huge Basic = t*x^a*y^b*z^c
Basic=t∗xa∗yb∗zc
各个因子都能解析成为一个Basic类,而最终表达式化简的结果一定是这样的:
E
x
p
r
=
B
a
s
i
c
1
+
B
a
s
i
c
2
+
B
a
s
i
c
3
+
.
.
.
+
B
a
s
i
c
n
\huge Expr = Basic_1 + Basic_2 + Basic_3+ ...+Basic_n
Expr=Basic1+Basic2+Basic3+...+Basicn
Basic这个类是我架构的核心,理解它基本上就理解了我的架构。详细的架构思路我会在在每次作业中具体描述。
第一次作业
架构
- 有了Basic类的支持,第一次作业就变得简单了
下图就是我第一次作业的架构,因为每个因子都能表示成为一个Basic或者Basics,所以我们使用以下具体架构。
- Factor、Term、Expr中的结果最终都是若干个Basic,所以每个类中保存一个Basic类的列表
- 合并展开的时候,针对的是每个类中的Basic类的列表,用这个来合并展开化简,那么就不必要过多的在意所谓的层次结构(事实上这是不对的)
第一次作业类图
第一次作业课后感想
- 第一次作业不该过于在意自己做对与否(当然,能做出来尽量做出来),或者说我们的目标并非在我们成功过了中测、强测那一刻就结束了,我们要学习的是面向对象这种抽象的思想(时至今日,第一单元结束,我认为自己才堪堪入门,了解其中九牛一毛),这将更有利于后面作业的分析与架构,也更能达到提升自我素养的目的
- 当我真正体会到所谓面向对象的时候,我不禁感叹这真是一个伟大的语言
第一次作业对我来说压迫力最大的。
因为我没有学习过先导课,并且暑假没有预习,以至于开学第一周过多而难的作业砸下来让我直接目瞪狗呆。 好在,巨大的压力给我带来了巨大的动力,不得不说,不同于过去一年多大学的摆烂生活,开学第一周是我大学生活以来最为充实的一周。
当时还什么都不懂,编写代码的逻辑全依赖于以前的算法竞赛的基础,并没有领略到面向对象编程OOP的真正含义,更多的,我是在面向过程编程,也就是没有转化到正确的编程角度。 从这方面来看的话,虽然第一次作业我拿到了一个不错的分数(一度让我沾沾自喜),但是我却是失败的,因为没有学到课程组想让我真正明白的东西——面向对象的思想、抽象的概念、层次架构的意义等等。
第二次作业
- 从第一次到第二次作业,这之间是一座大山,架构层面上的问题初显
要求增添
第二次作业在第一次作业的基础上,增加了如下要求:
- 允许出现新因子 sin(factor) cos(factor)
- 定义了函数,在化简合并的过程中要替换题目中的函数、函数最多不超过3个,函数中参数也是最多3个
输入例子如下 : 1 f ( x ) = s i n ( x ) + 1 需要化简的表达式如下: f ( x ) ∗ ∗ 2 则最简答案如下: s i n ( x ) ∗ ∗ 2 + 2 ∗ s i n ( x ) + 1 \huge \begin{align} \huge &输入例子如下:\\ \huge &1 \\ \huge &f(x)=sin(x)+1\\ \huge &需要化简的表达式如下:\\ \huge &f(x)**2\\ \huge &则最简答案如下:\\ \huge &sin(x)**2+2*sin(x)+1 \end{align} 输入例子如下:1f(x)=sin(x)+1需要化简的表达式如下:f(x)∗∗2则最简答案如下:sin(x)∗∗2+2∗sin(x)+1
解决方案
函数与新因子的引进给我们带来了许多的麻烦
函数参数的替换
首先,函数上我们必须明确函数名以及对应的每个参数是什么。
为了解决这个问题,我是用了HashMap<>
上图的含义如下:
- 我是用了两个HashMap<String,String>
- 第一个使用函数名字符串映射函数的参数字符串
- 第二个使用函数名字符串映射函数的表达式字符串
H a s h M a p 1 < f > = X Y Z H a s h M a p 2 < f > = ( X + 1 ) ∗ ∗ 2 \huge \begin{align} HashMap1<f> &= XYZ\\ HashMap2<f> &= (X+1)**2 \end{align} HashMap1<f>HashMap2<f>=XYZ=(X+1)∗∗2
注意:为了避免替换的时候出现问题,我们应该将形参xyz,替换为别的字符,我的选择是将其替换为大写,即XYZ
通过上述方法,我们很容易的可以将表达式内的函数变成一个没有函数的表达式, 到了这里,我们要做的就回到了第一次作业了,只需要将表达式解析一遍,然后返回解析后的Basic类的列表或集合即可
三角函数因子的处理
三角函数的引入其实并没有影响我们的架构,它只是让我们需要对Basic类做出调整。
B
a
s
i
c
=
t
∗
x
a
∗
y
b
∗
z
c
∗
H
a
s
h
S
e
t
<
T
r
i
F
u
c
>
\huge Basic = t*x^a*y^b*z^c*HashSet<TriFuc>
Basic=t∗xa∗yb∗zc∗HashSet<TriFuc>
做到这里,架构层面上的问题就出现了,三角函数的增加没有影响我的正确性 ,事实上添加一个类甚至没有在我主要的架构上进行过多的操作,但是其最致命的问题在于:
- 三角函数的引入让我的合并同类项变得非常困难
- 知识浅薄的我无法准确的实现不同Basic之间的合并,因为我很难确认两个Basic是否是等价的
无法合并的结果
三角函数的结果让我无法合并,最后的问题非常明显,就是我不再能够拿到任何一点性能分。
我和一个架构好的佬对比了一下一个相同输入:
- 他的输出长度: 9000+
- 我的输出长度: 100,0000+
第二次作业类图
第二次课后感想
就正确性而言,这次作业我依然0被hack过了。这之后我就开始了摆烂,感觉过了,问题就解决了,没有丝毫优化的劲头,更多的可能是不知道如何优化。
但是架构上的缺陷与漏斗让我不得不失去性能分。也就是这一次作业,我开始意识到一个好的架构的重要性。为我下一次重构埋下伏笔。
第三次作业
- 最初的架构在这一次作业中被彻底击碎(其实按照我现在来看,还是能缝缝补补,但是当时的我实在没有什么可以解决问题的方法)
要求增添
- 此次作业允许了求导因子的出现,但是只能出现一次
- 函数表达式中可以出现求导因子,同时也能出现其他已经定义的函数
举例输入如下 : 3 f ( x ) = x g ( x , y ) = x + y h ( x , y ) = d x ( f ( x ) + g ( x , y ) ) 需要化简的表达式: f ( x ) + g ( x ) + h ( x ) 最终的答案: 2 ∗ x + y + 2 \huge \begin{align} &举例输入如下:\\ &3\\ &f(x) = x\\ &g(x,y)=x+y\\ &h(x,y)=dx(f(x)+g(x,y))\\ &需要化简的表达式:\\ &f(x)+g(x)+h(x)\\ &最终的答案:\\ &2*x+y+2 \end{align} 举例输入如下:3f(x)=xg(x,y)=x+yh(x,y)=dx(f(x)+g(x,y))需要化简的表达式:f(x)+g(x)+h(x)最终的答案:2∗x+y+2
结构不适用
函数问题
函数问题还比较好解决,因为是使用的递归下降来分析文法,所以对这些函数的嵌套、递归等,都是非常自然而然能够完成的。
求导问题
按理说,求导不应该是一个过分苦难的操作,因为数据显示第三次作业,学生整体的通过情况是最好的。
但是,对于我的架构而言,这就是致命打击,以我当时的水平,我实在是无法想到有什么好的解决办法能够让我的架构支持求导
上机代码对我的影响
只有第三周的上机课,我看到了助教给的训练代码。当时正愁架构的我大为震惊。
”优雅!实在是!太优雅了!“ 这就是当时我的真实写照。 逻辑清晰、层次分明、结构明了,同时代码中使用了许多我之前不曾使用过的java语言的特性,这份代码对我来说,真的就是天外飞仙,天上馅饼,于是乎,我毅然决然的重构了。
- 重构!重构!还是xx的重构!
重构方案
按照训练代码的架构,如果不考虑合并化简,那么其实,第三次作业就很简单的能够迎刃而解了。这样的认识导致我第三次作业的时候不再有紧迫感,一点都不急着去实际操作。
函数处理
函数处理沿用第二次作业的处理方案,不需要修改。
合并化简
其实训练代码唯一的问题在于合并化简,如何能构造出一种优雅的化简策略,是我此次重构的重要目标。
于是拿着训练的架构,结合我自己的想法,我苦思冥想了一段时间。(真的只是再想,一点没动手),我确信自己找到了一个合适的解决方案,能够成功解决化简问题(说实话并不优美,但是无伤大雅)。
- 当我开始实现这个化简合并的时候已经是周日了,也就是提交的最后一天,当时的我迷之自信,相信自己能够在晚上8点前de完所有bug然后成功上交。、
- 在这之前,助教还来提醒过我让我早点提交。
- 这痛苦的重构过程我就不再赘述了,结果就是我没有交上最后一次作业。
第三次修改完后的类图
第三次课后感想
- 当中测开放结束的那一刹那,我后悔了,但我后悔的不是选择了重构,而是我明明可以做到,但因为自己的懒惰,又一次失败了
这之后的第二天,我成功de出了之前的bug,但是为时已晚。
回想起来,我并不后悔选择了重构,因为在这次重构中,我真的学到了很多东西。
最初的架构因为相关知识学习没有到位,有着种种缺陷和漏洞,即使后面随着知识的增加,意识到了相关的问题,但是实在是无能为力,对最初的代码无从下手。 自然而然,这过程中学到的东西,也没办法应用到代码中去。
但是这次重构,我把先前想用而未用的到接口、继承、深克隆、重写equals方法等种种学到的知识都应用到了代码中去,相较于第一次架构,这实在是一次赏心悦目的重构,即便它以失败收场。
总结
关于重构
虽然我重构失败了,但是我仍然建议意识到自己架构有问题的同学进行重构,重构的过程真的能学到很多东西。
与其缝缝补补,写一堆让自己看着都难受的东西,尝试一下推倒重来,建一栋优雅舒适的大厦,你会感受到不一样的情绪。
结束语
总而言之,言而总之,第一单元最后一次未交上很遗憾。
但是,就像我开头说的,第一单元的要点,想让你学到的并非不择手段的去做完这三次作业。
我们更应该紧扣课程名称面向对象,在这个过程中:
- 真正地去领会面向对象的思想
- 明白什么样的抽象适合于我们的解决方案
- 同时如何得到良好的、清晰的、易扩展的架构
我觉得从同学那听来的这句话很有道理:
然建议意识到自己架构有问题的同学进行重构,重构的过程真的能学到很多东西。
与其缝缝补补,写一堆让自己看着都难受的东西,尝试一下推倒重来,建一栋优雅舒适的大厦,你会感受到不一样的情绪。
结束语
总而言之,言而总之,第一单元最后一次未交上很遗憾。
但是,就像我开头说的,第一单元的要点,想让你学到的并非不择手段的去做完这三次作业。
我们更应该紧扣课程名称面向对象,在这个过程中:
- 真正地去领会面向对象的思想
- 明白什么样的抽象适合于我们的解决方案
- 同时如何得到良好的、清晰的、易扩展的架构
我觉得从同学那听来的这句话很有道理:
- ”其实这一路重构下来的同学,可能远比我们这些修修补补写下来的同学学到的东西多“