NCHU OOP BLOG1--电梯调度程序

NCHU OOP BLOG1--电梯调度程序

目录

1.前言
2.设计与分析
3.踩坑心得
4.改进建议
5.总结

正文

1.前言

  这三次大作业主要围绕的对电梯的调度来展开,调度算法为LOOK算法,实际上,比现实中的一些电梯所用算法更简单。
  其中,第一次作业难度最大,后面两次作业进行迭代并不难;
  考点主要是类和对象,以及类与类之间的关系,还包括java中List容器的使用,和正则表达式的使用。
  题量其实并不大,并且给了类图做参考,提供了很大的帮助。但是,如果看轻了这几次作业,没有花费足够的时间去分析题目,做设计等,就很难在DeadLine之前完成,然后就Die了,后面进行迭代的时候就更跟不上进度了,因此还是要重视每一次的大作业啊。

2.设计与分析

第一次大作业(共5道题):

  重点在最后一道:电梯调度问题,因而前面几道便简单分析一下

1.前面两题:

  7-1 NCHU_求身份证号校验位、7-2 NCHU_求解一元二次方程

7-1
分析:
 对输入的数字字符串进行操作,根据公式,计算出最后一位的数字
这道题的难点是在如何对字符串的每位上的数字进行处理,我采用将字符串转化为字符数组的方法,从而对数组的每个元素进行处理,用java中字符串自带的toCharArray()函数;

7-2
分析:
 求二元方程的解,包括一个解,两个解,无解等多种情况,主要对二次方程a b c三个系数进行计算,因此创建了一个代表二次方程的类QuadraticEquation,a b c为该类的三个私有属性

 该类除了基本的构造方法,get、set方法,还包括得到Discriminant方法,得到两个root的方法,用于得到两个根

  小结:其实这两道题主要是让我们熟悉java的语言风格,帮助我们掌握基本的类和对象的知识,能够用类创建对象,并对对象进行操作(都只有一个类,就不做类图了)

2.第三四题:

 7-3 NCHU_正则表达式训练-验证码校验 7-4 NCHU_正则表达式训练-QQ号校验

7-3
分析:
 实际上就是判断一个字符串是否符合相应的格式(四位数字或字母),很标准的用正则表达式求解的题目
 这道题的难点在于:正则表达式部分该如何写,其实就是"\w{4}",\w可以匹配大小写字母和数字

7-4
分析:
 判断输入的数字字符串是否符合条件(5-15位、每位数字:0-9、首位不为0),基本于7-3相同,就是正则表达式更复杂了些(不重复了)

3.第五题:

7-5 NCHU_单部电梯调度程序

7-5

题面:

  设计一个电梯类,具体包含电梯的最大楼层数、最小楼层数(默认为1层)当前楼层、运行方向、运行状态,以及电梯内部乘客的请求队列和电梯外部楼层乘客的请求队列,其中,电梯外部请求队列需要区分上行和下行。
  电梯运行规则如下:电梯默认停留在1层,状态为静止,当有乘客对电梯发起请求时(各楼层电梯外部乘客按下上行或者下行按钮或者电梯内部乘客按下想要到达的楼层数字按钮),电梯开始移动,当电梯向某个方向移动时,优先处理同方向的请求,当同方向的请求均被处理完毕然后再处理相反方向的请求。电梯运行过程中的状态包括停止、移动中、开门、关门等状态。当电梯停止时,如果有新的请求,就根据请求的方向或位置决定移动方向。电梯在运行到某一楼层时,检查当前是否有请求(访问电梯内请求队列和电梯外请求队列),然后据此决定移动方向。每次移动一个楼层,检查是否有需要停靠的请求,如果有,则开门,处理该楼层的请求,然后关门继续移动。
  使用键盘模拟输入乘客的请求,此时要注意处理无效请求情况,例如无效楼层请求,比如超过大楼的最高或最低楼层。还需要考虑电梯的空闲状态,当没有请求时,电梯停留在当前楼层。
  请编写一个Java程序,设计一个电梯类,包含状态管理、请求队列管理以及调度算法,并使用一些测试用例,模拟不同的请求顺序,观察电梯的行为是否符合预期,比如是否优先处理同方向的请求,是否在移动过程中处理顺路的请求等。为了降低编程难度,不考虑同时有多个乘客请求同时发生的情况,即采用串行处理乘客的请求方式(电梯只按照规则响应请求队列中当前的乘客请求,响应结束后再响应下一个请求),具体运行规则详见输入输出样例。

输入输出格式:

输入格式:
第一行输入最小电梯楼层数。
第二行输入最大电梯楼层数。
从第三行开始每行输入代表一个乘客请求。

 电梯内乘客请求格式:<楼层数>
 电梯外乘客请求格式:<乘客所在楼层数,乘梯方向>,其中,乘梯方向用UP代表上行,用DOWN代表下行(UP、DOWN必须大写)。
 当输入“end”时代表输入结束(end不区分大小写)。
输出格式:
模拟电梯的运行过程,输出方式如下:

 运行到某一楼层(不需要停留开门),输出一行文本:
 Current Floor: 楼层数 Direction: 方向
 运行到某一楼层(需要停留开门)输出两行文本:
 Open Door # Floor 楼层数
 Close Door


题目及代码分析
依据题目而言(其中标粗部分为我认为比较重要的点):
 只需要设计一个电梯类,属性为最大楼层,最小楼层,当前楼层,状态,方向;

 下面写该类的函数
首先,基本的构造方法,get、set方法;

其次,依据标粗部分至少包括:
 1.“有乘客对电梯发起请求”,判断状态是否改变的ifchangestatue,如果请求队列中还有请求,返回true,没有返回false;
 2.“根据请求的方向或位置决定移动方向”,判断电梯运行方向的pan_direction,返回String类型(方向);
 3.“优先处理同方向的请求”,得到要去的楼层的getgofloor
 4.“检查是否有需要停靠的请求”,判断该楼层是否停止的ifstop
 5.“处理该楼层的请求”,处理楼层的removeout和removein
 6. 改变楼层是电梯楼层变化的函数的changecurrentfllor,依据是否达到目的楼层和运行方向,来确定是增加还有减少楼层;
 7. 输出的函数的print_nostop print_isstop,停靠与不停靠分开输出;

最后,在主函数中:
 我采用正则表达式来提取关键信息(END),数据存储在外(内)请求数组中,再作为参数传入,对请求进行操作

  PS:依据题目要求,只设计了一个类(同样不做类图),后面的大作业对该类拆分,因而不会再分析算法,只对迭代的地方进行分析。

  下面,让我们来看看该类的圈复杂度:最大为23,平均为5.14,比良好的代码2.0~4.0的范围超了不少,主要是在得到请求楼层的函数中(在其他函数中也有这种情况),为了防止正则表达式访问越界的情况,写了多次的分支,使复杂度增加,以后我会尽量避免这种情况,可以用更好的方法来代替,一劳永逸。分支更多,逻辑也会更复杂,这应该是我之后的训练方向。

  
  

第二次大作业(共3道题):

1.前面两题:

  7-1 点与线(类设计)、7-2 NCHU_汽车风挡玻璃雨刷问题(类设计)
 这两道题主要是根据题意进行类设计,包括类的属性,方法,以及类与类之间的关系,熟悉设计流程,帮助我们完成7-3电梯类的设计。

7-1
分析
 这道题让我们设计两个类,包括点类和线类,其中点与线是聚合关系,线是整体,点是局部,除基本的构造方法和get、set方法外,线类中还有得到线长(两点间距离)的方法。
 这题的难点在于,类与类之间的关系(聚合),如何在语义上理解聚集关系,同时语法上实现生命周期的不同。

7-2
分析
 这道题让我们设计一个简单的雨刷系统,实现雨刷速度的控制,每一个挡位下只能进行一次加或减;
 为了符合单一职责原则和迪米特法则,设计了:雨刷类、控制杆类、刻度盘类、以及最重要的控制类,通过控制类来对其他三个类进行操作,因而控制类与其他类之间的关系为关联。

2.第三题:

7-3 NCHU_单部电梯调度程序(类设计):迭代练习

7-3
题面:

  对之前电梯调度程序进行迭代性设计,目的为解决电梯类职责过多的问题,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客请求类、队列类以及控制类
  电梯运行规则与前阶段单类设计相同,但要处理如下情况:

  乘客请求楼层数有误,具体为高于最高楼层数或低于最低楼层数,处理方法:程序自动忽略此类输入,继续执行
乘客请求不合理,具体为输入时出现连续的相同请求,例如<3><3><3>或者<5,DOWN><5,DOWN>,处理方法:程序自动忽略相同的多余输入,继续执行,例如<3><3><3>过滤为<3>

注意:本次作业类设计必须符合如上要求(包含但不限于乘客请求类、电梯类、请求队列类及控制类,其中控制类专门负责电梯调度过程),凡是不符合类设计要求此题不得分,另外,PTA得分代码界定为第一次提交的最高分代码(因此千万不要把第一次电梯程序提交到本次题目中测试)。

输入输出格式:

输入格式:
第一行输入最小电梯楼层数。
第二行输入最大电梯楼层数。
从第三行开始每行输入代表一个乘客请求。

 电梯内乘客请求格式:<楼层数>
 电梯外乘客请求格式:<乘客所在楼层数,乘梯方向>,其中,乘梯方向用UP代表上行,用DOWN代表下行(UP、DOWN必须大写)。
 当输入“end”时代表输入结束(end不区分大小写)。
输出格式:
模拟电梯的运行过程,输出方式如下:

 运行到某一楼层(不需要停留开门),输出一行文本:
 Current Floor: 楼层数 Direction: 方向
 运行到某一楼层(需要停留开门)输出两行文本:
 Open Door # Floor 楼层数
 Close Door

题目及代码分析
  与上一次的电梯相比,这一次的电梯调度的算法没变,还是LOOK算法,但是对电梯类进行了拆分,为实现单一职责原则这次我添加了:外部请求类、请求队列类、控制类、枚举类状态、枚举类方向

  在控制类中实现第一次作业中的对电梯的各种操作,将电梯与外部请求的队列解耦,电梯与队列没有直接关系,“不要和陌生人讲话”,符合迪米特法则(LOD)

  而外部请求类则是为了方便将请求类中的外部请求进行操作,两个枚举类型则方便对电梯中的属性进行表示和改变值;


  因为要对请求队列中的元素进行删除,可能还需要扩大,所以用链表数组LinkList,方便操作,便将字符串数组转换为了链表数组;

这就是这次电梯程序的全部思路,下面来对代码进行分析:

 从类图可以看出,就和我上方分析的一样,控制类与电梯、队列类为关联关系,外部队列类与枚举类方向为关联关系,电梯类与两个枚举类型为关联关系;
 这次的代码整体上方法改动不大(只是改变了方法所在的类),改动最大的是主函数:
1.改变了判断输入是否符合规范的方法,从第一次判断字符的个数改成了用正则表达式判断是否满足输入的要求;

2.新增了判断输入是否正确的方法,去除请求楼层超过范围的请求,以及连续多次输入相同的请求;

3.最后将电梯类中的,改变楼层的函数写入主函数中,在主函数中实现电梯的调度。

			while(controller.moving())
			{
				int gofloor=controller.getnextfloor();
				if(controller.shouldstop(gofloor))
				{
					if(controller.elevator.getDirection()!=Direction.IDLE)
						controller.showState();
					controller.openDoors();
					int currentfloor=controller.elevator.getCurrentfloor();if(controller.queue.externalQuests.size()>0&&controller.queue.externalQuests.get(0).getFloor()==currentfloor)
					{
						Direction direct=controller.queue.externalQuests.get(0).getDirection();
						if(direct!=controller.elevator.getDirection())
						{
							controller.elevator.setDirection(direct);
							controller.removeRequest(currentfloor);
						}
						else {
							controller.removeRequest(currentfloor);
							controller.determineDirection();
						}
					}
					else 
					{
						controller.removeRequest(currentfloor);
						controller.determineDirection();
					}
					
					controller.removeRequest(currentfloor);
					controller.determineDirection();
					if(controller.elevator.getDirection()==Direction.UP)
					{
						currentfloor++;
						controller.elevator.setCurrentfloor(currentfloor);
					}
					else if(controller.elevator.getDirection()==Direction.DOWN)
					{
						currentfloor--;
						controller.elevator.setCurrentfloor(currentfloor);
					}
					//System.out.println(controller.queue.externalQuests.size());
					//System.out.println(controller.queue.internalQuests.size());
				}
				else
				{	
					controller.showState();
					int currentfloor=controller.elevator.getCurrentfloor();
					if(controller.elevator.getDirection()==Direction.UP)
						controller.elevator.setCurrentfloor(++currentfloor);
					if(controller.elevator.getDirection()==Direction.DOWN)
						controller.elevator.setCurrentfloor(--currentfloor);
				}
			}

 最后,我们来分析一下代码的圈复杂度,看看与上次相比有没有什么变化,有什么可以改进的地方

 从上图可以看出,这次的圈复杂度还是有很大改变的;
 其中,变化最大就是最大复杂度,由原来的23增大到了36,增大了不少,主函数在判断输入是否正确时使用了太多if_else语句,使分支数量增加;
 平均复杂度处在了正常范围,再将函数再细化,可以使复杂度再降低一些;
 最后,就是注释语句太少了,远远低于正常范围,代码的规范做的还是不到位,还需要继续改进,需要在日常的练习中培养写注释的习惯,也可以看看一些大厂的代码规范,用这些来约束自己;
  那么,以上就是大作业二的全部分析,作业三敬请期待!

第三次大作业(共3道题):

1.前面两题:

  7-1 NCHU_销售步枪问题 7-2 蒙特卡罗方法求圆周率
 这两道题的目的与第二次类似,为了进一步锻炼我们对类与对象,和面向对象的相关知识的掌握,这两道也不是很难,简单分析一下。

7-1
分析
 枪械销售的题目,其实在c语言时已经写过了,只是让你用面向对象的编程语言翻译一遍,设计四个类,三个具体类,一个控制类,也是为了满足单一职责原则和迪米特法则,降低类与类之间的耦合。由题目所给的类图,可以明确的知道要怎么设计类,大大降低了难度。


7-2
分析
 这道题涉及一个新的算法,用蒙特卡罗方法求圆周率,但是降低了难度,算法和类图已经给出,只要看懂题目意思,基本上能解决。虽然题目本身不难,但是题目背后涉及了:统计模拟,近似求解的过程,这是一个很重要的思想,可以运用到多种问题的求解上。

  

2.第三题:

7-3 NCHU_单部电梯调度程序(类设计-迭代):迭代练习

7-3
题面:

  对之前电梯调度程序再次进行迭代性设计,加入乘客类(Passenger),取消乘客请求类,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客类、队列类以及控制类;

  电梯运行规则与前阶段相同,但有如下变动情况:

  乘客请求输入变动情况:外部请求由之前的<请求楼层数,请求方向>修改为<请求源楼层,请求目的楼层>
  对于外部请求,当电梯处理该请求之后(该请求出队),要将<请求源楼层,请求目的楼层>中的请求目的楼层加入到请求内部队列(加到队尾
注意:本次作业类设计必须符合如上要求(包含但不限于设计电梯类、乘客类、队列类以及控制类),凡是不符合类设计要求此题不得分,另外,PTA得分代码界定为第一次提交的最高分代码(因此千万不要把第一次及第二次电梯程序提交到本次题目中测试)。

输入输出格式:

输入格式:
第一行输入最小电梯楼层数。
第二行输入最大电梯楼层数。
从第三行开始每行输入代表一个乘客请求。

 电梯内乘客请求格式:<楼层数>
 电梯外乘客请求格式:<请求源楼层,请求目的楼层>,其中,请求源楼层表示乘客发起请求所在的楼层,请求目的楼层表示乘客想要到达的楼层。
 当输入“end”时代表输入结束(end不区分大小写)。
输出格式:
 模拟电梯的运行过程,输出方式如下:

运行到某一楼层(不需要停留开门),输出一行文本:
Current Floor: 楼层数 Direction: 方向
运行到某一楼层(需要停留开门)输出两行文本:
Open Door # Floor 楼层数
Close Door

题目及代码分析
  与上次的电梯相比,这一次电梯的算法并未改变,但是修改了输入的方式,从而导致要删去外部请求类,改为乘客类,然后内部与外部请求都可以用乘客类更方便表示,可以用一种泛型表示两种数组;
  “请求目的楼层加入到请求内部队列(加到队尾)”,因而在请求类中,内部请求添加一个add函数,用于接收外部请求的目的楼层;

  因此这次的代码,我增加了乘客类:包含基本的构造方法、get、set方法,以及获取外部请求方向的getdirection方法(在内部请求时,默认sourceFloor属性为0);

public class Passenger {
Integer sourceFloor;
Integer destinationFloor;
public Passenger() {
	// TODO Auto-generated constructor stub
}
public Passenger(Integer sourceFloor,Integer destinationFloor ) {
	this.sourceFloor=sourceFloor;
	this.destinationFloor=destinationFloor;
}
public Passenger(Integer destinationFloor ) {
	this.destinationFloor=destinationFloor;
}
public Integer getSourceFloor() {
	return sourceFloor;
}
public void setSourceFloor(Integer sourceFloor) {
	this.sourceFloor = sourceFloor;
}
public Integer getDestinationFloor() {
	return destinationFloor;
}
public void setDestinationFloor(Integer destinationFloor) {
	this.destinationFloor = destinationFloor;
}
public Direction getdirection() {
	if(this.sourceFloor>this.destinationFloor)
		return Direction.DOWN;
	else if(this.sourceFloor<this.destinationFloor)
		return Direction.UP;
	else return Direction.IDLE;
}
}

 最后,在主函数中,需要简单修改一下检测输入的几个步骤,问题不大。

  题目分析就到这了,下面来看下类图:

 从上面的类图可以看出,类之间的关系并不复杂,一样,通过控制类将请求类和电梯类联系起来,避免了电梯与请求类的直接联系;
  这次的题目其实改动并不大,主要是围绕外部请求输入改变做的调整,如果前面的两次的电梯认真完成了,这题基本上没问题。
  最后,来看下这个电梯的圈复杂度:

 与上一次的电梯相比,这一次有了点进步,最大复杂度减小6,减少了一些分支,平均复杂度也在正常的范围内,注释的比例也在增大,但是问题依旧存在,最大复杂度仍然远远高于正常值,还有需要改进的地方。
 以上,就是第三次大作业的全部分析。

让我们恭喜这一Part圆满完成!!!

3.踩坑心得

  回顾这三周的练习,想想就很不容易,踩了不少坑,每次踩进去都要花不少功夫才能出来,真是心酸史了。(分析三次电梯调度程序)

习题五——7-5

(1)首先,最大的坑就是,“我以为是这样的”,掉坑里几天后,还是段老师点醒我们,永远不要让“你以为”占据上风,因为“你以为的都是错的”,一切都要根据需求来设计。
  刚开始,没有理解算法,“以为”是将所有请求都考虑进去,寻找当前楼层下,最优的请求,然后就写了三天,后面才知道只需要考虑每个队列的第一个请求,这是代码改的第一遍;
  然后,理解了算法后,又没有好好做设计,导致代码逻辑很复杂,又大部分推翻重新来过,第三遍才有了雏形;

(2)其次,有一个经常踩的坑就是,没有搞清楚需求,导致总是得不出正确的结果;

 从图上可以很明显的看到,一连串的非零返回,就是在需求分析时,没有注意到,“end不区分大小写”,导致从输入就有了错误;

(3)最后,也是需求的坑,格式错误,导致答案错误,就算逻辑都对了,但是输出出了问题,一切都没用,输出格式也是需求的一种,要重视需求分析,这对任何一个项目而言都非常重要,需要我们花费更多的功夫,而不是一拿到题目,就直接上手敲代码。

习题六——7-3

  这道题目有一个测试点没过,这一路上非常的心酸,问题应该是出在有一些情况没有考虑到,导致测试点没过;
 (1)在修改完第一次作业的代码后,刚开始的提交是答案错误,先比较了两个测试用例是一样的,还以为是哪里的逻辑问题没发现,用Eclipse调试了几遍,没有问题,当时是有点崩溃的;

 (2)第二天,从主函数出发,发现需求中,要求我们“去除连续的相同请求”,但是我的去除了所有相同的请求,所以进行了修改,结果部分正确,当时确实很心累;

 (3)最后,还是回到了逻辑问题,比较几个补充测试用例,发现不同,重新修改,反复了三四遍,直至所有已知的测试用例都相同;但提交后,还是部分正确,当时整个人都麻了;

 (4)说实话,现在还是有点懵,不知道问题在哪,但是整个过程下来,收获还是很多的:
 首先,在排除简单错误的情况下,第一要考虑需求分析有没有出现错误,有没有忽略的地方;
 其次,就是很好的锻炼了我对Eclipse的调试功能的使用,能够合理的设置断点;
 最后,也很好的磨练了我的心态,保持平常心,不过分追求分数,也不轻易放弃;

习题七——7-3

  这次习题没有前两次踩的坑多,完成的也很快。简单谈一下,我的心得:
 首先,一定要快刀斩乱麻,有一些函数自己都理不清思路,改不明白,干脆直接删掉重写,没必要死磕在上面,有时候换个思路,能更快的解决问题;
 其次,就是可以适当的找别人的帮助,因为你踩的坑,可能是别人踩过的,这样才能更快的解决问题。大家一起讨论,反而效率更高;
 最后,就是一定要坚持,当时感觉人生无望,可真正走过了这个坎,会觉得一切不过如此,人生是一个阶段一个阶段闯过去的。

!!!下一Part——让我们来到改进阶段!!!

4.改进建议

  对于代码的改进,前面已经谈论了一些,主要是:
(1)增加更多的注释,几次的注释率都很低,提高代码的可读性;
(2)增加请求不符合格式,以及其他特殊情况的处理,边界的处理等等,使代码可以应对更极端和复杂的情况;
(3)修改一些函数,处理一下if——else的情况,可以做特殊处理,比如面对外部请求队列为空的情况,不再重复判断是否size>0,而是在一开始确定为空时,直接返回或做特殊处理,减少代码的分枝数,从而减少圈复杂度;

最后一部分——恭喜!!!

5.总结

  第一阶段的练习结束了,但是我们的学习旅程还没有结束,现在来总结一下,这段时间收获了什么,简述下一阶段的安排;
(1)这一阶段,充分训练了我对正则表达式的使用、List容器的使用(实现了从无到有的进程),熟悉了这门面向对象的语言的语法,以及java语言的一些原则,拉近了我与java这门两月前还陌生的语言的距离;
(2)必不能少说的就是,对心性的磨练,不断地打击你,又拉起你,反反复复,内心都更强大了(方便接受下一阶段的打击);
(3)也有一些需要改进和深入学习的地方:类与类之间的关系。组合、聚合、关联三者还是不能很好的区分,同时在语法上,对组合、聚合关系的区分也掌握的不是很好;
(4)下一阶段,在巩固这一阶段学习的基础上,将侧重学习java继承与多态的知识,更深入的了解java的设计原则,不断的运用在实践中;

结束!!!
我们下次再见!!!

posted @ 2025-04-17 17:26  等时圆~  阅读(186)  评论(1)    收藏  举报