NCHU-PTA电梯问题 blog
前言
关于课程学习:
到这周为止,我已经学习java语言八周时间了,其中第五到七周的PTA电梯问题确实是我到现在遇到的最大的问题,以至于仍未解决,一次次的编译过程,编译的结果从最开始的编译错误,到答案错误再到运行超时,虽然到最后没有获得完全性的胜利,但是在这个跌倒再不断爬起的过程中,我也在java语言学习中获得了不可多得的宝贵经验。
关于题目的总结:
通过这几次题目我学习到的知识点:
1.正确清晰地创建类与对象
2.分别设置队列处理内外部请求
3.建立各类之间的分工关系
4.对于输入输出以及异常情况的处理
这三次的题目呈迭代式,每个题目都相关甚大,说实话,第一次看到的时候是束手无策,难度还是蛮大的,主要集中在如何正确地处理内外部请求逻辑问题以及避免运行超时的情况。
题目分析与心得改进
PTA第五次作业
设计一个电梯类,具体包含电梯的最大楼层数、最小楼层数(默认为1层)当前楼层、运行方向、运行状态,以及电梯内部乘客的请求队列和电梯外部楼层乘客的请求队列,其中,电梯外部请求队列需要区分上行和下行。
电梯运行规则如下:电梯默认停留在1层,状态为静止,当有乘客对电梯发起请求时(各楼层电梯外部乘客按下上行或者下行按钮或者电梯内部乘客按下想要到达的楼层数字按钮),电梯开始移动,当电梯向某个方向移动时,优先处理同方向的请求,当同方向的请求均被处理完毕然后再处理相反方向的请求。电梯运行过程中的状态包括停止、移动中、开门、关门等状态。当电梯停止时,如果有新的请求,就根据请求的方向或位置决定移动方向。电梯在运行到某一楼层时,检查当前是否有请求(访问电梯内请求队列和电梯外请求队列),然后据此决定移动方向。每次移动一个楼层,检查是否有需要停靠的请求,如果有,则开门,处理该楼层的请求,然后关门继续移动。
使用键盘模拟输入乘客的请求,此时要注意处理无效请求情况,例如无效楼层请求,比如超过大楼的最高或最低楼层。还需要考虑电梯的空闲状态,当没有请求时,电梯停留在当前楼层。
请编写一个Java程序,设计一个电梯类,包含状态管理、请求队列管理以及调度算法,并使用一些测试用例,模拟不同的请求顺序,观察电梯的行为是否符合预期,比如是否优先处理同方向的请求,是否在移动过程中处理顺路的请求等。为了降低编程难度,不考虑同时有多个乘客请求同时发生的情况,即采用串行处理乘客的请求方式(电梯只按照规则响应请求队列中当前的乘客请求,响应结束后再响应下一个请求),具体运行规则详见输入输出样例。
输入格式:
第一行输入最小电梯楼层数。
第二行输入最大电梯楼层数。
从第三行开始每行输入代表一个乘客请求。
电梯内乘客请求格式:<楼层数>
电梯外乘客请求格式:<乘客所在楼层数,乘梯方向>,其中,乘梯方向用UP代表上行,用DOWN代表下行(UP、DOWN必须大写)。
当输入“end”时代表输入结束(end不区分大小写)。
输出格式:
模拟电梯的运行过程,输出方式如下:
运行到某一楼层(不需要停留开门),输出一行文本:
Current Floor: 楼层数 Direction: 方向
运行到某一楼层(需要停留开门)输出两行文本:
Open Door # Floor 楼层数
Close Door
源码静态分析

| 类型 | 数据 |
|---|---|
| 行数 | 220 |
| 语句数 | 138 |
| 分支语句占比 | 25.4% |
| 方法调整语句 | 90 |
| 注释行占比 | 9.1% |
| 类和接口数量 | 3 |
| 每个类的平均方法数 | 5 |
| 每个方法平均语句 | 7.2 |
类设计方面:
刚开始不熟悉分类的好处,只总共设置了两个类
Elevator 类:代表了电梯的核心功能和属性,负责管理电梯的运行状态、请求队列以及处理各种请求。它还包含一个内部类ExternalRequest,用于表示外部请求,记录请求的楼层和方向。
Main 类:负责读取用户输入,并将请求传递给Elevator类进行处理。
反思与心得:
1.在后期回顾会发现,这次的代码请求队列安排不合理,如下,只使用一个队列来存储所有外部请求,并且没有区分上行和下行请求,这导致在处理请求时需要多加条件语句判断,使得代码分工不够明确
// 外部请求类
static class ExternalRequest {
int floor; // 请求楼层
String direction; // 请求方向(UP/DOWN)
public ExternalRequest(int floor, String direction) {
this.floor = floor;
this.direction = direction;
}
}
//对输入数据进行解读
public void addRequest(String request) {
if (request.startsWith("<") && request.endsWith(">")) {
// 去掉尖括号
request = request.substring(1, request.length() - 1);
if (request.contains(",")) {
// 外部请求
String[] parts = request.split(",");
int floor = Integer.parseInt(parts[0]);
String dir = parts[1];
if (isValidFloor(floor)) {
externalRequests.add(new ExternalRequest(floor, dir));
}
} else {
// 内部请求
int floor = Integer.parseInt(request);
if (isValidFloor(floor)) {
internalRequests.add(floor);
}
}
}
}
2、从分析代码复杂度的方面看,如下表格,有些请求处理条件判断的逻辑复杂,且未运用到枚举的方法,使代码的安全性和可读性不高,这些都需要进一步的改进
| 方法名 | 复杂度 | 语句数 | 最大块深度 |
|---|---|---|---|
| Main.main() | 3 | 12 | 3 |
| Elevator.run() | - | - | - |
| shouldStop() | - | - | 4 |
| findNextFloor() | - | - | 3 |
3.可改进的地方:
代码可读性和可维护性
注释占比为9.1%,增加更多的注释来解释关键逻辑,有助于后期代码的维护,提高代码的可读性
加入正则表达式,将一些硬编码的字符串(如 "UP", "DOWN", "STILL")定义为常量,提高代码的可维护性
第六次PTA作业
对之前电梯调度程序进行迭代性设计,目的为解决电梯类职责过多的问题,类设计要求遵循单一职责原则(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
参考类图

源码静态分析

| 类型 | 数据 |
|---|---|
| 行数 | 272 |
| 语句数 | 166 |
| 分支语句占比 | 18.1% |
| 方法调整语句 | 109 |
| 注释行占比 | 12.5% |
| 类和接口数量 | 4 |
| 每个类的平均方法数 | 7 |
| 每个方法平均语句 | 5 |
类设计方面:
1.Elevator 类:负责电梯的楼层、方向和状态管理
2.ExternalRequests 类:表示外部请求,包括楼层和方向
3.RequestQueue 类:管理内部请求和外部请求,相比较第一次,合理地分离内外请求管理逻辑,避免与电梯逻辑混合
4.Controller 类:处理请求调度和电梯移动逻辑,这一部分在第一次中包含在主类中,这次引入分层设计,将它的业务逻辑从主类剥离,使得代码可读性更高
反思与心得:
1.相比于第一次,第二次中我加强了对输入的正则校验,对内外部请求加强校验,如下
//对输入进行校验
if (!data.matches("<\\d+,\\s*(UP|DOWN)>")) {
System.out.println("Wrong Format");
continue;
}
2.新增逻辑:在 RequestQueue.addInternalRequest() 和 addExternalRequest() 中,通过比较最后一个请求lastInternalRequest和lastExternalRequest,来避免重复添加相同请求

新增规则:Controller.determineDirection() 方法中,根据内部和外部请求的楼层与当前楼层的关系,动态切换电梯方向,就比如电梯向上时,如果所有请求楼层低于当前楼层,则切换为下行。但此处为处理所有可能遇到的情况,使用了多层嵌套,复杂度大大加深,因为代码过长,此处仅展示情况一和情况二

3.复杂度分析:
分支语句占比:18.1%,但 exStop() 和 determineDirection() 中存在多层 if 嵌套,可读性下降
块深度:最大块深度为 3
方法调用深度:processRequests() → move() → determineDirection() → shouldStop() → inStop()/exStop(),调用链较长,可读性下降
| 方法名 | 复杂度 |
|---|---|
| Controller.processRequests() | 1 |
| Controller.move() | 3 |
| Controller.exStop() | 5 |
| Main.main() | 4 |
第七次PTA作业
对之前电梯调度程序再次进行迭代性设计,加入乘客类(Passenger),取消乘客请求类,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客类、队列类以及控制类,具体设计可参考类图。
电梯运行规则与前阶段相同,但有如下变动情况:
乘客请求输入变动情况:外部请求由之前的<请求楼层数,请求方向>修改为<请求源楼层,请求目的楼层>
对于外部请求,当电梯处理该请求之后(该请求出队),要将<请求源楼层,请求目的楼层>中的请求目的楼层加入到请求内部队列(加到队尾)
输入格式:
第一行输入最小电梯楼层数。
第二行输入最大电梯楼层数。
从第三行开始每行输入代表一个乘客请求。
电梯内乘客请求格式:<楼层数>
电梯外乘客请求格式:<请求源楼层,请求目的楼层>,其中,请求源楼层表示乘客发起请求所在的楼层,请求目的楼层表示乘客想要到达的楼层。
当输入“end”时代表输入结束(end不区分大小写)。
输出格式:
模拟电梯的运行过程,输出方式如下:
运行到某一楼层(不需要停留开门),输出一行文本:
Current Floor: 楼层数 Direction: 方向
运行到某一楼层(需要停留开门)输出两行文本:
Open Door # Floor 楼层数
Close Door
参考类图

代码静态分析

| 类型 | 数据 |
|---|---|
| 行数 | 397 |
| 语句数 | 211 |
| 分支语句占比 | 18.5% |
| 方法调整语句 | 118 |
| 注释行占比 | 11.8% |
| 类和接口数量 | 5 |
| 每个类的平均方法数 | 7 |
| 每个方法平均语句 | 11 |
反思与心得

1.心得:
如图所示,添加乘客类后的测试用例,第一个测试用例可以通过,但是第二个的运行结果的第一行输出就与预期结果不同,我怀疑是自己的逻辑出了问题,于是我多次尝试修改调整初始状态的输出逻辑,在 Elevator 类中,把电梯的初始方向设置为 "UP",确保输出的第一行状态信息为初始楼层和上行方向,避免出现 "STILL" 这种不符合预期的情况。同时,Controller 类中的 processRequests 方法也会在开始处理请求时,正确输出电梯的初始状态。这样就能保证程序输出的第一行符合预期,且后续的电梯调度逻辑保持不变。
2.改进的方向:
后续对于各个部分的队列分析逻辑进行简化和对于其可拓展性的提高
比如:

原 determineDirection 方法使用多个嵌套来分别处理内外部请求队列的不同情况,逻辑较为复杂,代码可读性和可维护性较差。而且它分别对内部请求队列不为空、外部请求队列不为空以及两个队列都不为空等多种情况进行判断,导致代码分支较多。
改进后:代码将内部请求和外部请求合并到一个 List 中,统一进行处理。通过计算所有请求楼层中的最高楼层和最低楼层,然后根据当前楼层与最高、最低楼层的关系来确定电梯的运行方向,使得逻辑更加简洁,减少了代码的复杂度,也提高了可读性。
总结
收获
在这三次作业的迭代过程中,我从一开始的迷茫无措,到渐渐地掌握正则表达式校验输入,到逐步对主类的职责分离,到对单一职责原则(SRP)不断了解,再到增强代码的可复用性和可维护性,获得了很多宝贵的经验
一道完整的题目做下来,这也使我明白,对于不懂的方法不可乱用,先要搞清楚所用方法的逻辑以及如何使用和修改,否则中途出错就会茫然无措,不知道从哪下手
做题目遇到过不去的坎时也不能剑走偏锋,需要沉下心来,再捋捋自己的逻辑,试着从不同的角度思考
建议
希望老师可以多设置一些得分点和测试用例,这样可以帮助我们理解题目逻辑,一定程度上提高我们对答题的信心,谢谢老师

浙公网安备 33010602011771号