0.写在最前面的废话
度量分析的工具真的好难搞!到现在我的metrics也无法正常使用,可以创建它的透视图,但是不能再工程的属性里找到它......得,我现在只能展示类关系图谱(逃)。
OO给我的感觉就像是雨夜登山一般,“登山”本已举步维艰,大晚上的还漆黑一片,得“边爬边摸索”,要是走了面向过程的“歪路”,也难免重头再来。加之各种条条框框如大雨般倾盆而下,一不小心就弄个“泥泞不堪”,如果不慎落了“无效作业”的大坑,也只能叫苦不迭但无力回天。
嗨,这么一写还突然觉得,OO有点意思。
1.第一次作业
第一次作业是要写多项式的加减,我的程序的大体结构如下:
其中,Main是主类,负责调用其他的类,并运行程序;
Check类是用来检测输入的字符串是否合法的,在这里主要使用了正则表达式来判断;
Operation类是用来实现多项式加减的,主要是一个大循环,一个字符一个字符向后读。
第一次作业的公测错了两个点,互测没出问题,第一个是爆栈,原因是我的正则表达式就是一个长串来匹配整个的输入,导致在最大长度的输入情况下程序出了问题。把输入的几十个多项式按花括号拆开,再进行正则匹配即可解决。
第二个点是没有考虑-0还是什么关于正负号的一个小点,后来仔细阅读指导书后,大呼之遂心服口服,这很好的提醒了我在以后的作业中要仔细考虑各种情况以及仔细阅读指导书,勤于思考各种非法的情况。
我的第一次作业的代码量比较少,可能勉强算是一个有点,但第一次写面向对象的程序,总是有些四不像的感觉,不会使用构造函数,把所有的功能都放在了类的方法中,形似面向过程。
2.第二次作业
第二次作业是写一个服从“傻瓜调度”的电梯,即根据指令的时间顺序来控制电梯的运动,我的程序的大体结构如下:
其中,Request类是用来处理单条需求的,通过正则表达式判断指令是否合法,进而判断指令是哪种类型(FR+UP,FR+DOWN,ER),然后将指令分解,分别去判断时间和楼层是否越界。因为考虑到时间可能超过int型的范围并且会有很多的前导零,我用了一个循环来算时间...
for(int i=0; i<s.length; i++ ) {
if(s[i]!= '+') {
NowTime *= 10;
NowTime += (s[i] - '0');
}
}
Queue类是用来循环读入指令的,调用了Request类,对单条指令进行处理,并把它们分别存在对应的数组中。
Elevator类是用来记录电梯的运行状态的,包括电梯现在所处的楼层,下一个楼层,当前时间,下一个时间,电梯运行时间,运行状态等。
Floor类是用来记录楼层信息的,但其实实际操作中我几乎没用到它,因为Elevator类里的东西就够用了。
Scheduler类是用来执行电梯调度的,实例化了Queue类的对象来读入指令,也实例化了Elevator类的对象来记录电梯的运行状态,并设置了一些其他的属性来模拟电梯的运行。
private double[] ERButton = new double[20];
private double[] FRUPButton = new double[20];
private double[] FRDOWNButton = new double[20];
上面三个按钮数组用来模拟电梯里面和楼层上的按钮。开始时我想用boolean型的数组,但是发现那样只能记录按钮的状态,不能记录按钮状态改变的时间,所以我后来改用double型,记录每个按钮被释放的时间,即这个按钮在哪一个时刻可以被再次使用,在这个时刻之前,如果有人发出了需要这个按钮的指令,则被视为同质指令。
if(queue.RequestTypeArray[valid] == 1) {
if(queue.TimeArray[valid] <= FRUPButton[queue.FloorArray[valid]]) {
System.out.println("#This input is similar:"+(queue.InputArray[i]));
IfSimilar = true;
}
}
拿一段代码来说明,比如一条指令的类型为1(FR+UP),那么比较读入指令的发出时刻与该指令的对应按钮的释放时间,就可以判断这条指令是不是同质(在这之前先判断是否合法)。这样就不需要预先知道后面的指令是什么,简化了代码的复杂度。
如果它不同质,那么就可以产生一个对应的输出,并改变某个按钮的释放时间,部分代码如下:
if(queue.RequestTypeArray[valid] == 1) {
FRUPButton[queue.FloorArray[valid]] = elevator.NextTime;
}
比如这条指令的类型是1,那么就将FRButton中对应按钮的时间设置为elevator.NextTime(即经过计算后的电梯达到下一个楼层的时间),这样就可以让程序不断的循环运作了~
第二次作业的公测我没有错测试点,互测错了一个,是:输出如果超过了int范围并且是小数,那么我的输出就会把它的小数部分给省略掉,后来我寻找原因,发现在这个地方:
System.out.print(BigDecimal.valueOf(elevator.NextTime));
我为了使输出不是科学记数法的形式,我用了BigDecimal,但不知为啥这里会出这样的幺蛾子......
第二次作业我花了很久的时间去思考,主要在想如何去判断各种同质的状况,后来经过不断的摸索,并且有意识地去模拟真实电梯的属性和方法(在我理解里的面向对象吧),我选择用按钮这个概念来记录电梯的被触发不同状态后的终止时刻,想明白了这个之后再去写代码就顺畅了许多。有了这次的经验,我在写第三次作业之前花了更多的时间去思考。
3.第三次作业
第三次作业的电梯是在第二次作业的基础上考虑“捎带”这一规则,我本以为只需要小改一下就成,后来在实际操作中我才发现自己错的离谱......先展示一下程序的大体结构:
其中,除了SubScheduler类以外,其他几个类与第二次作业基本相同,不赘述;
SubScheduler是继承了Scheduler父类的子类,说来惭愧,我这次作业几乎只干了一件事儿:重写了Scheduler父类的Run函数......
因为电梯要捎带,那么电梯的运行就不能只是发生在相邻的两条指令之间了,因为后面的指令或者时刻靠后的指令完全有可能要先执行,判断哪些指令要先执行就是最大的难点所在。我先很自然地想到我们使用的电梯是什么样的,在运动到某一楼层前,如果接收到这个楼层发出的信号,如果方向相同便可以停靠,那么关键点有两个,一个是位置,一个是时间。我最初想到的是以时间为主线,直接以0.5s为最小的度量,每过0.5秒记录电梯的位置和运行状态,然后去队列中搜索有没有这个时刻或之前发出的同层的指令,后来因为各种考虑放弃之。
于是便选择了先遍历一下读入指令后面的指令,如果这条指令满足可捎带的条件,执行之,然后把它从请求队列中剔除,并改变一些按钮被释放的时间以及楼层、到达时刻等属性,其中一部分大概像下面这样:
else if(elevator.Condition.equals("UP")) {
if(queue.RequestTypeArray[j] == 1) {
if((queue.FloorArray[j] <= MaxFloor) && (queue.FloorArray[j] > elevator.NowFloor)) {
ExtraTime = (queue.FloorArray[j] - elevator.NowFloor)*0.5;
for(int k=(elevator.NowFloor); k<(queue.FloorArray[j]); k++) {
ExtraTime += ExtraFloorArray[k];
}
if(queue.TimeArray[j] < (elevator.NowTime + ExtraTime)) {
if(queue.TimeArray[j] <= FRUPButton[queue.FloorArray[j]] ) {
//同质
UsedArray[j] = true;
System.out.println("#SAME["+(queue.ValidArray[j])+"]");
}
else {
FRUPButton[queue.FloorArray[j]] = (elevator.NowTime + ExtraTime);
ExtraFloorArray[queue.FloorArray[j]] = 1.0;
//加入捎带的额外数组
UsedArray[j] = true;
ExtraConditionArray[queue.FloorArray[j]] = "UP";
ExtraOrderArray[queue.FloorArray[j]][ExtraSameNumberArray[queue.FloorArray[j]]] = j;
ExtraSameNumberArray[queue.FloorArray[j]]++;
}
}
}
}
很繁琐,分了电梯运行的各种状态以及指令的各种类型来分类讨论,这里把主指令之后的指令存入一个新的数组中,而不是直接输出,因为它后面还可能有比它还要提前执行的指令,所以输出也变得很繁琐了......
if(elevator.Condition.equals("UP")) {
for(int m=elevator.NowFloor; m<=MaxFloor; m++) {
if(ExtraFloorArray[m] == 1.0) {
for(int n=0; n<ExtraSameNumberArray[m]; n++) {
if(queue.RequestTypeArray[ExtraOrderArray[m][n]] == 1) {
System.out.print("[FR,");
System.out.print(queue.FloorArray[ExtraOrderArray[m][n]]);
System.out.print(",UP,");
System.out.print(queue.TimeArray[ExtraOrderArray[m][n]]);
System.out.print("]/(");
System.out.print(m);
System.out.print(",");
System.out.print(ExtraConditionArray[m]);
System.out.print(",");
System.out.print(BigDecimal.valueOf(RunTime));
System.out.println(")");
}
我在主请求结束后进行这个输出的循环,大体思路是上面的程序段记录了每个楼层的停靠状态,之后遍历楼层,把有标记的楼层按要求输出。这是一种以位置为主轴的方法吧。因为一层楼中可能会开若干次门,所以只能建立一个二维数组ExtraOrderArray来记录某层楼的可能出现的若干次开门的请求。
很幸运,第三次作业的公测和互测都没有出BUG,可以说是很振奋人心了。但想到之后要写多线程的电梯,感觉程序难免还是要修改,还是很令人头大。
这次作业虽然重点只是放在了重写Run函数上,但周六日两天恰好有事情不能写OO,结果导致我的周一二三过的相当艰难,通宵数宿,苦不堪言,不堪回首,触目惊心......想不想得清楚是一方面,更重要的是一些小的细节的改动会牵一发而动全身,本来想要微调结果不得不大刀阔斧的改一通,其中折磨可想而知。这也让我吸取到了很宝贵的经验:一、拖延症害死人,能早点解决的事儿别把它养肥了;二、磨刀不误砍柴工,在头脑清醒时先把问题想清楚,方法想完善,必要时可以手写一下流程框架图什么的,这样说不定就能事半功倍。
总的来说,OO还是让人收获颇丰的,别的不说,就是以后得去“搬砖”,基本的编程能力还是要有的,毋庸置疑,OO锻炼了我的编程能力;其次,在思考和对问题的分析上,我可以有意识地用面向对象的思路去思考问题(或许我还并不能正确认识什么是面向对象),这是一种有趣的思考过程,我总感觉这样幻想出来的对象还有点可爱......
最后祝大家永不被判无效作业!实在太痛了,也希望以后的OO能升级一下无效作业的判断机制吧。