7-3 NCHU_单部电梯调度程序
第一次作业
1.分析题目
题目:
设计一个电梯类,具体包含电梯的最大楼层数、最小楼层数(默认为1层)当前楼层、运行方向、运行状态,以及电梯内部乘客的请求队列和电梯外部楼层乘客的请求队列,其中,电梯外部请求队列需要区分上行和下行。
电梯运行规则如下:电梯默认停留在1层,状态为静止,当有乘客对电梯发起请求时(各楼层电梯外部乘客按下上行或者下行按钮或者电梯内部乘客按下想要到达的楼层数字按钮),电梯开始移动,当电梯向某个方向移动时,优先处理同方向的请求,当同方向的请求均被处理完毕然后再处理相反方向的请求。电梯运行过程中的状态包括停止、移动中、开门、关门等状态。当电梯停止时,如果有新的请求,就根据请求的方向或位置决定移动方向。电梯在运行到某一楼层时,检查当前是否有请求(访问电梯内请求队列和电梯外请求队列),然后据此决定移动方向。每次移动一个楼层,检查是否有需要停靠的请求,如果有,则开门,处理该楼层的请求,然后关门继续移动。
使用键盘模拟输入乘客的请求,此时要注意处理无效请求情况,例如无效楼层请求,比如超过大楼的最高或最低楼层。还需要考虑电梯的空闲状态,当没有请求时,电梯停留在当前楼层。
请编写一个Java程序,设计一个电梯类,包含状态管理、请求队列管理以及调度算法,并使用一些测试用例,模拟不同的请求顺序,观察电梯的行为是否符合预期,比如是否优先处理同方向的请求,是否在移动过程中处理顺路的请求等。为了降低编程难度,不考虑同时有多个乘客请求同时发生的情况,即采用串行处理乘客的请求方式(电梯只按照规则响应请求队列中当前的乘客请求,响应结束后再响应下一个请求),具体运行规则详见输入输出样例。
输入格式:
第一行输入最小电梯楼层数。
第二行输入最大电梯楼层数。
从第三行开始每行输入代表一个乘客请求。
电梯内乘客请求格式:<楼层数>
电梯外乘客请求格式:<乘客所在楼层数,乘梯方向>,其中,乘梯方向用UP代表上行,用DOWN代表下行(UP、DOWN必须大写)。
当输入“end”时代表输入结束(end不区分大小写)。
输出格式:
模拟电梯的运行过程,输出方式如下:
运行到某一楼层(不需要停留开门),输出一行文本:
Current Floor: 楼层数 Direction: 方向
运行到某一楼层(需要停留开门)输出两行文本:
Open Door # Floor 楼层数
Close Door
分析:
首先拿到这个题目,最开始的步骤就是划分类,明确有几个类,以及类的作用和功能,从题目可知,它一直在强调什么乘客,请求,队列,电梯运行的,看得是雨里雾里,所以此刻不要放弃,一段段看,画出频繁出现的主体,在完后就知道了要有电梯类,外部请求类,内部请求类,外部请求对列,内部请求队列,还有最重要的就是Main类,它统筹整个运行过程,用来掌管整个系统的运行。

其次,要开始分析每个类到底有什么作用,分析时要从小到大分析,从易到难分析:
-
内部请求类:它只有一个信息就是请求楼层。
-
外部请求类:请求方向,请求楼层。
-
内部请求队列:这里我使用的是链表的形式,后面我就会直接利用LinkList<>会更加的简单。
-
外部请求对列:用它来管理请求的加入与删除。
-
电梯类:在我们的代码空间中,我们不用去关心具体它是怎么样的,我们只需要我们要用的,在题目中,我们电梯有最高楼层,最低楼层,运行方向等基本属性,还有它的动作,即向上,向下或者停止三种运行状态,我们只用关心这些
-
Main类:它就要开始着手处理电梯是如何运行的,电梯要运行,没有电梯不行吧,所以有一个电梯类的主体,有电梯没有乘客请求,它怎么运行,所以要有两个请求队列吧,即外部与内部请求对列吧,它的动作有什么,抽象出来不就是解决我要处理哪个请求,这个请求我怎么处理。
最后,开始写程序了。
2.写代码
简单的类我在这里就不在写了,主要关心的是处理逻辑,即Main类的operation怎么就实现了如何处理电梯运行的逻辑。
operation()
public void operation(){while(this.qqList.head!=null&&this.iqList.head!=null){if(this.qqList.head.direction.equals(this.e.direction)&&!this.qqMovedirection().equals(this.e.direction)){this.handleIQ();}else if(this.iqMovedirection().equals(this.qqList.head.direction)){if(this.qqDistance()<=this.iqDistance())this.handleQQ();elsethis.handleIQ();}else {if(this.e.direction.equals(this.qqList.head.direction))this.handleQQ();elsethis.handleIQ();}}// 处理剩余的请求while (this.qqList.head != null || this.iqList.head != null) {if (this.qqList.head != null) {this.handleQQ();} else {this.handleIQ();}}}
}
这个代码具体的处理逻辑我就是参考这个博客的NCHU_单部电梯调度程序_单步电梯调度-CSDN博客
就是电梯每次判断要处理哪个是只考虑两个队列的第一个请求,与电梯当前运行方向的,这个非常重要,对于队列,他只考虑第一个请求,后面的目前不会考虑,等处理完,后面的请求向前变为第一个,在开始考虑,这是一个典型的先进先出的队列。
3.代码质量
Metrics Details For File 'Main.java'
--------------------------------------------------------------------------------------------Parameter Value
========= =====
Project Directory D:\IDEACODE\Elevator\Elevator\src\qwwq\
Project Name Elevator
Checkpoint Name Baseline
File Name Main.java
Lines 271
Statements 174
Percent Branch Statements 25.3
Method Call Statements 52
Percent Lines with Comments 7.0
Classes and Interfaces 6
Methods per Class 3.33
Average Statements per Method 8.25
Line Number of Most Complex Method 8
Name of Most Complex Method Main.main()
Maximum Complexity 13
Line Number of Deepest Block 50
Maximum Block Depth 5
Average Block Depth 1.74
Average Complexity 2.54--------------------------------------------------------------------------------------------
Most Complex Methods in 6 Class(es): Complexity, Statements, Max Depth, CallsElevator.Elevator() 1, 7, 2, 1
Elevator.moveDown() 1, 3, 2, 1
Elevator.moveUp() 1, 3, 2, 1
Elevator.openDoor() 1, 1, 2, 1
IQList.IQdequeue() 3, 4, 3, 0
IQList.IQenqueue() 3, 5, 3, 0
IQList.IQList() 1, 2, 2, 0
IQNode.IQNode() 1, 2, 2, 0
Main.main() 13, 33, 5, 20
QQList.QQdequeue() 3, 4, 3, 0
QQList.QQenqueue() 3, 5, 3, 0
QQList.QQList() 1, 2, 2, 0
QQNode.QQNode() 1, 3, 2, 0--------------------------------------------------------------------------------------------
Block Depth Statements0 15
1 61
2 61
3 30
4 5
5 2
6 0
7 0
8 0
9+ 0
--------------------------------------------------------------------------------------------

4.心得
刚开始写这个题目的时候我是很不想写的,一点头绪都没有,此时切记,不要烦躁,要静下心来,虽然你可能认为我在打乱话,但是要相信这是只一时的,在写出来过测试点的时候是很高兴的。不要把类分的太大了,导致类的功能不明确,写起来的方法一下调用这个类,一下调用哪个类,代码将会越来越看不懂。
第二次作业
1.分析题目
题目:
对之前电梯调度程序进行迭代性设计,目的为解决电梯类职责过多的问题,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客请求类、队列类以及控制类,具体设计可参考如下类图。
电梯运行规则与前阶段单类设计相同,但要处理如下情况:
乘客请求楼层数有误,具体为高于最高楼层数或低于最低楼层数,处理方法:程序自动忽略此类输入,继续执行
乘客请求不合理,具体为输入时出现连续的相同请求,例如<3><3><3>或者<5,DOWN><5,DOWN>,处理方法:程序自动忽略相同的多余输入,继续执行,例如<3><3><3>过滤为<3>
输入格式:
第一行输入最小电梯楼层数。
第二行输入最大电梯楼层数。
从第三行开始每行输入代表一个乘客请求。
电梯内乘客请求格式:<楼层数>
电梯外乘客请求格式:<乘客所在楼层数,乘梯方向>,其中,乘梯方向用UP代表上行,用DOWN代表下行(UP、DOWN必须大写)。
当输入“end”时代表输入结束(end不区分大小写)。
输出格式:
模拟电梯的运行过程,输出方式如下:
运行到某一楼层(不需要停留开门),输出一行文本:
Current Floor: 楼层数 Direction: 方向
运行到某一楼层(需要停留开门)输出两行文本:
Open Door # Floor 楼层数
Close Door
分析:
第一次的代码过的基础上,我们只需要考虑错误和连续相同的请求,我们只要在请求入队的哪个方法上加上判断要入对的请求是否与最后一个请求相等,如果相等就不要入队。
2.写代码
有效性
// 处理乘客请求(从第三行开始)for (int i = 2; i < inputs.size(); i++) {String input = inputs.get(i).trim();// 移除可能存在的尖括号input = input.replace("<", "").replace(">", "");// 处理电梯内乘客请求(格式:数字)if (input.matches("^\\d+$")) {int floor = Integer.parseInt(input);if (floor >= minFloor && floor <= maxFloor) {requestQueue.addInternalRequest(floor);} else {//System.out.println("楼层 " + floor + " 超出范围,忽略该请求");}}// 处理电梯外乘客请求(格式:数字,方向)else if (input.matches("^\\d+\\s*,\\s*\\d+\\s*$")) {String[] parts = input.split("\\s*,\\s*");int floor = Integer.parseInt(parts[0]);int requestFloor = Integer.parseInt(parts[1]);if (floor >= minFloor && floor <= maxFloor&&requestFloor>=minFloor&&requestFloor<=maxFloor) {ExternalRequest externalRequest = new ExternalRequest(floor, requestFloor);requestQueue.addExternalRequest(externalRequest);} else {// System.out.println("楼层 " + floor + " 超出范围,忽略该请求");}} else {// System.out.println("无效的输入格式: " + input);}}
我使用了正则表达式的方式来判断输入的楼层请求是否符合要求并调用加入函数把他们加入到相应队列中。
重复输入判断
void addInternalRequest(int floor){if(this.internalRequest.isEmpty() ||floor!=this.internalRequest.getLast())this.internalRequest.add(floor);}void addExternalRequest(ExternalRequest externalRequest){if(this.externalRequests.isEmpty()||externalRequest.getFloor()!=this.externalRequests.peekLast().getFloor()||externalRequest.getRequestFloor()!=this.getExternalRequests().getLast().getRequestFloor())this.externalRequests.add(externalRequest);}
只要保证新加入的请求与队尾的请求不是一样的就可以了。
3.代码质量
Metrics Details For File 'Main.java'
--------------------------------------------------------------------------------------------Parameter Value
========= =====
Project Directory D:\IDEACODE\Elevator2\Elevator\src\wewa\
Project Name
Checkpoint Name Baseline
File Name Main.java
Lines 318
Statements 197
Percent Branch Statements 21.3
Method Call Statements 129
Percent Lines with Comments 10.4
Classes and Interfaces 7
Methods per Class 6.29
Average Statements per Method 3.07
Line Number of Most Complex Method 8
Name of Most Complex Method Main.main()
Maximum Complexity 15
Line Number of Deepest Block 44
Maximum Block Depth 5
Average Block Depth 1.74
Average Complexity 2.03--------------------------------------------------------------------------------------------
Most Complex Methods in 5 Class(es): Complexity, Statements, Max Depth, CallsController.Controller() 1, 2, 2, 0
Controller.Controller() 1, 1, 2, 0
Controller.externalRequestDirect() 3, 5, 2, 4
Controller.externalRequestDistance() 1, 1, 2, 4
Controller.getElevator() 1, 1, 2, 0
Controller.getQueue() 1, 1, 2, 0
Controller.internalRequestDirect() 4, 6, 2, 6
Controller.internalRequestDistance() 1, 1, 2, 3
Controller.processRequest() 12, 16, 5, 28
Controller.setElevator() 1, 1, 2, 0
Controller.setQueue() 1, 1, 2, 0
Elevator.Elevator() 1, 4, 2, 0
Elevator.getCurrentFloor() 1, 1, 2, 0
Elevator.getDirection() 1, 1, 2, 0
Elevator.getElevatorInstance() 1, 2, 2, 0
Elevator.getMaxFloor() 1, 1, 2, 0
Elevator.getMinFloor() 1, 1, 2, 0
Elevator.getState() 1, 1, 2, 0
Elevator.isValidFloor() 4, 4, 2, 0
Elevator.moveDown() 1, 3, 2, 1
Elevator.moveUp() 1, 3, 2, 1
Elevator.openDoor() 1, 1, 2, 1
Elevator.setCurrentFloor() 1, 1, 2, 0
Elevator.setDirection() 1, 1, 2, 0
Elevator.setState() 1, 1, 2, 0
ExternalRequest.ExternalRequest() 1, 3, 2, 1
ExternalRequest.getDirection() 1, 1, 2, 0
ExternalRequest.getFloor() 1, 1, 2, 0
ExternalRequest.getRequestFloor() 1, 1, 2, 0
ExternalRequest.setDirection() 3, 4, 2, 0
Main.main() 15, 26, 5, 18
RequestQueue.addExternalRequest() 4, 2, 2, 9
RequestQueue.addInternalRequest() 3, 2, 2, 3
RequestQueue.getExternalRequests() 1, 1, 2, 0
RequestQueue.getInternalRequest() 1, 1, 2, 0
RequestQueue.getQueueInstance() 1, 2, 2, 0
RequestQueue.RequestQueue() 1, 0, 0, 0
RequestQueue.setExternalRequests() 1, 1, 2, 0
RequestQueue.setInternalRequest() 1, 1, 2, 0--------------------------------------------------------------------------------------------
Block Depth Statements0 15
1 65
2 86
3 20
4 9
5 2
6 0
7 0
8 0
9+ 0
--------------------------------------------------------------------------------------------

4.心得
得益于第一次将类图分析的很好,基本上与题目要求一样,我就只需要改一下加入函数就好了。
第三次作业
题目:
对之前电梯调度程序再次进行迭代性设计,加入乘客类(Passenger),取消乘客请求类,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客类、队列类以及控制类,具体设计可参考如下类图。

电梯运行规则与前阶段相同,但有如下变动情况
乘客请求输入变动情况:外部请求由之前的<请求楼层数,请求方向>修改为<请求源楼层,请求目的楼层>
对于外部请求,当电梯处理该请求之后(该请求出队),要将<请求源楼层,请求目的楼层>中的请求目的楼层加入到请求内部队列(加到队尾)
输入格式:
第一行输入最小电梯楼层数。
第二行输入最大电梯楼层数。
从第三行开始每行输入代表一个乘客请求。
电梯内乘客请求格式:<楼层数>
电梯外乘客请求格式:<请求源楼层,请求目的楼层>,其中,请求源楼层表示乘客发起请求所在的楼层,请求目的楼层表示乘客想要到达的楼层。
当输入“end”时代表输入结束(end不区分大小写)。
输出格式:
模拟电梯的运行过程,输出方式如下:
运行到某一楼层(不需要停留开门),输出一行文本:
Current Floor: 楼层数 Direction: 方向
运行到某一楼层(需要停留开门)输出两行文本:
Open Door # Floor 楼层数
Close Door
分析:
在上一次的代码上,我们只需要在外部请求处理后,再将外部请求的后半部分加入内部请求队列。
2.写代码
handleExternalRequest()
void handleExternalRequest(){if (this.queue.getExternalRequests().isEmpty()) {return;}while(this.queue.getExternalRequests().peekFirst().getFloor()!=this.elevator.getCurrentFloor()){if(this.queue.getExternalRequests().peekFirst().getFloor()>this.elevator.getCurrentFloor())this.elevator.moveUp();elsethis.elevator.moveDown();}this.elevator.openDoor();removeExternalRequests(this.elevator.getCurrentFloor());}
removeExternalRequest()
void removeExternalRequests(int currentFloor){if(!this.queue.getExternalRequests().isEmpty() &&this.elevator.getCurrentFloor()==this.queue.getExternalRequests().getFirst().getFloor()) //保护空指针{this.queue.addInternalRequest(this.queue.getExternalRequests().getFirst().getRequestFloor());//将处理后的外部请求进入内部this.queue.getExternalRequests().removeFirst();}if(!this.queue.getInternalRequest().isEmpty() &&this.elevator.getCurrentFloor()==this.queue.getInternalRequest().peekFirst()) //保护空指针this.queue.getInternalRequest().removeFirst();}
但是在我改完代码提交的时候,第一个测试点一直过不去,但是样例都是正确的,我一直都想不出来,在参考了他人的代码时,我突然注意到他们处理剩余的请求写在了一个大的循环里面,而我的分成了两个,是不是因为这而出现了死循环等情况,最后在询问了Deepseek后,他指出是空指针问题导致我的程序有误
void processRequest(){while(this.queue.getExternalRequests().peekFirst() != null || this.queue.getInternalRequest().peekFirst() != null) {// 如果两个队列都有请求,使用复杂决策逻辑if (this.queue.getExternalRequests().peekFirst() != null && this.queue.getInternalRequest().peekFirst() != null) {// 原有的复杂决策逻辑if (this.queue.getExternalRequests().peekFirst().getDirection() == this.elevator.getDirection() && this.externalRequestDirect() != this.elevator.getDirection()) {handleInternalRequest();} else if (internalRequestDirect() == this.queue.getExternalRequests().peekFirst().getDirection()) {if (externalRequestDistance() >= internalRequestDistance()) {handleInternalRequest();} elsehandleExternalRequest();} else if (this.queue.getExternalRequests().peekFirst().getDirection() == this.elevator.getDirection())handleExternalRequest();elsehandleInternalRequest();}// 如果只有一个队列有请求,简单处理else {if (this.queue.getExternalRequests().peekFirst() != null) {this.handleExternalRequest();} else {this.handleInternalRequest();}}}/*使用下面这个会导致空指针泄露具体的原因还是得问deepseekwhile(this.queue.getExternalRequests().peekFirst() != null && this.queue.getInternalRequest().peekFirst() != null) {if (this.queue.getExternalRequests().peekFirst().getDirection() == this.elevator.getDirection() && this.externalRequestDirect() != this.elevator.getDirection()) {handleInternalRequest();} else if (internalRequestDirect() == this.queue.getExternalRequests().peekFirst().getDirection()) {if (externalRequestDistance() >= internalRequestDistance()) {handleInternalRequest();} elsehandleExternalRequest();} else if (this.queue.getExternalRequests().peekFirst().getDirection() == this.elevator.getDirection())handleExternalRequest();elsehandleInternalRequest();}// 处理剩余的请求while (this.queue.getExternalRequests().peekFirst() != null || this.queue.getInternalRequest().peekFirst() != null) {if (this.queue.getExternalRequests().peekFirst()!= null) {this.handleExternalRequest();} else {this.handleInternalRequest();}}*/}
3.代码质量
Metrics Details For File 'Main.java'
--------------------------------------------------------------------------------------------Parameter Value
========= =====
Project Directory D:\IDEACODE\text\PTA\src\PTA\
Project Name
Checkpoint Name Baseline
File Name Main.java
Lines 349
Statements 200
Percent Branch Statements 22.0
Method Call Statements 138
Percent Lines with Comments 17.2
Classes and Interfaces 7
Methods per Class 6.29
Average Statements per Method 3.11
Line Number of Most Complex Method 8
Name of Most Complex Method Main.main()
Maximum Complexity 15
Line Number of Deepest Block 248
Maximum Block Depth 6
Average Block Depth 1.80
Average Complexity 2.08--------------------------------------------------------------------------------------------
Most Complex Methods in 5 Class(es): Complexity, Statements, Max Depth, CallsController.Controller() 1, 2, 2, 0
Controller.Controller() 1, 1, 2, 0
Controller.externalRequestDirect() 3, 5, 2, 4
Controller.externalRequestDistance() 1, 1, 2, 4
Controller.getElevator() 1, 1, 2, 0
Controller.getQueue() 1, 1, 2, 0
Controller.internalRequestDirect() 4, 6, 2, 6
Controller.internalRequestDistance() 1, 1, 2, 3
Controller.processRequest() 14, 17, 6, 32
Controller.setElevator() 1, 1, 2, 0
Controller.setQueue() 1, 1, 2, 0
Elevator.Elevator() 1, 4, 2, 0
Elevator.getCurrentFloor() 1, 1, 2, 0
Elevator.getDirection() 1, 1, 2, 0
Elevator.getElevatorInstance() 1, 2, 2, 0
Elevator.getMaxFloor() 1, 1, 2, 0
Elevator.getMinFloor() 1, 1, 2, 0
Elevator.getState() 1, 1, 2, 0
Elevator.isValidFloor() 4, 4, 2, 0
Elevator.moveDown() 1, 3, 2, 1
Elevator.moveUp() 1, 3, 2, 1
Elevator.openDoor() 1, 1, 2, 1
Elevator.setCurrentFloor() 1, 1, 2, 0
Elevator.setDirection() 1, 1, 2, 0
Elevator.setState() 1, 1, 2, 0
ExternalRequest.ExternalRequest() 1, 3, 2, 1
ExternalRequest.getDirection() 1, 1, 2, 0
ExternalRequest.getFloor() 1, 1, 2, 0
ExternalRequest.getRequestFloor() 1, 1, 2, 0
ExternalRequest.setDirection() 3, 4, 2, 0
Main.main() 15, 26, 5, 18
RequestQueue.addExternalRequest() 4, 2, 2, 9
RequestQueue.addInternalRequest() 3, 2, 2, 3
RequestQueue.getExternalRequests() 1, 1, 2, 0
RequestQueue.getInternalRequest() 1, 1, 2, 0
RequestQueue.getQueueInstance() 1, 2, 2, 0
RequestQueue.RequestQueue() 1, 0, 0, 0
RequestQueue.setExternalRequests() 1, 1, 2, 0
RequestQueue.setInternalRequest() 1, 1, 2, 0--------------------------------------------------------------------------------------------
Block Depth Statements0 17
1 65
2 84
3 15
4 13
5 5
6 1
7 0
8 0
9+ 0
--------------------------------------------------------------------------------------------

4.心得
在通不过测试点的时候,要注意空指针问题。