第一次阶段性OOP题目集总结性Blog
前言:
- 基础题目训练说明
- 第一次基础题目有四道,题量适中,考察知识点主要是正则表达式的基本用法,以及简单分类讨论逻辑与java基础语法,考察学生能都否从讨论判断转变到便捷的正则表达式的使用,逻辑上的难度为⭐。
- 第二次基础题目有两道,题量较少,考察知识点主要是类设计,举了生活中常见例子雨刷类初步引出控制类“中介”的用法为迭代类设计做好铺垫。逻辑上的难度为⭐⭐。
- 第三次基础题目集有两道,题量较少,考察知识点是类图设计与Math静态库函数调用,如abs,rand,要求学生能读懂题目意思,根据题目的方法与设计估算Π值。逻辑上的难度为⭐⭐⭐。
- 迭代题目训练说明
- 第一次迭代题目主要考察学生能否读懂题目意思,考察知识点主要是java基本语法,电梯调度算法(简化版LOOK算法),结合了现实生活中电梯的实际运行情况,但是又不完全相同,所以在理解题目意思上具有一定的困难。虽然为了减少题目难度并没有考查时间问题,但是本人认为进栈出栈,每次考虑只考虑头部队列也是一种考虑了时间的体现。逻辑难度为⭐⭐⭐⭐⭐。
- 第二次迭代题目集考察知识点主要是类与类的设计与读入数据合法性校验判断,能否正确理解控制类在程序中的作用。算法与第一次迭代相差不大,逻辑难度⭐⭐⭐。
- 第三次迭代题目集考察知识点与第二次相差不大,主要改变了读取数据的方式,逻辑难度⭐⭐⭐。
上面三点对于刚开始学习java的学生来说都具有一定的挑战性。第二次迭代就是在第一次的基础上增加了类与类要求的设计与读入数据合法性校验判断,相对来说只要第一次理解了题目的算法,难度并不大,第三次迭代就是在第二次迭代的基础上改变了类与类的设计,改变了输入方式,但是总体算法不变,潜心下来依旧可以较快解决。下面分别在三次迭代作业中分享本人的心得。😊
第一次 NCHU_单部电梯调度程序😣
-
题目需求
- 设计一个电梯类,包含状态管理、请求队列管理以及调度算法,要求能实现给出样例测试以及测试点。
1. 设计与分析
1.PowerDesigner类图
点击查看类图设计
设计解释
- 采用链表结构分别存储内外部队列楼层以及方向
- 将判断方向,电梯移动,调度函数全部放进Elevator类中
- 采用正则表达式读取输入信息并存入链表
2.SourceMontor报表
点击查看分析报告图示
点击查看方法具体复杂度
3.思路解释与分析
- 本人首先将读取到的字符串存入数组,利用正则表达式非捕获分组分为判断是否读取到DOWN或UP判断存入内部队列还是外部队列,任意内部队列或外部队列不为空就一直从队列中读取。(具体代码见附录)
- 在电梯调度算法的实现上本人分别设计了响应队列请求函数,移动函数,方向判断函数等等,下面分析主要响应队列函数以及移动函数。
🔽为两个重要函数解释说明
- 响应队列函数 首先判断队列是否存在为空的情况,1.若内外队列都为空则直接返回。2.若存在内部队列或外部队列为空则读取不为空的头队列的目的楼层target,并且判断当前楼层与目的楼层的位置关系从而实现通过改变index变量动态调整移动方向.3.若内外部队列都不为空则需要比较内外头队列楼层与当前楼层的位置关系,如果大于当前楼层就是UP,如果小于当前楼层则为DOWN,如果等于当前楼层则不需要判断方向直接调用开门函数并且移除相应的头队列。若方向都与当前方向相同则比较哪个距离当前楼层较近,否则以方向优先的原则进行读取目标楼层。
- 移动函数 主要逻辑通过flag变量来判断target来自内部队列还是外部队列。1.如果来自内部队列那么通过while遍历到该楼层直接开门,其中result变量用来判断当前楼层是否开过门,防止连续两次打印该楼层信息。2.如果来自外部队列那么就要判断电梯方向是否与请求方向相同,如果相同则开门,还有一种开门情况就是请求方向与运行方向不同,此时目标楼层高于或低于内部队列的楼层,(即达到了当前方向的最高楼层或最低楼层)所以要转向后再开门。
2. 踩坑心得
- 踩坑:题目意思理解错误,本人一开始认为电梯调度算法是考虑内部队列和外部队列所有楼层,获取其中的最高楼层或者最低楼层,同一个方向遍历到最高楼层或最低楼层之后再转向判断。但是事实的情况却是只要考虑头队列的情况,再通过讨论情况正确实现题目要求。
- 心得:因为没有理解题目意思导致浪费了大量时间在算法的思考与设计上,所以在以在后来的迭代作业中要秉持潜心读题,研究透题再开始编码会事半功倍。
- 这里总结常见的错误点当提交测试发现1.非零返回时无非就是读取没有结束,或者队列没有读取完毕,或者mani函数定义不够规范。
- 在大规模编码完成之后,本人算法逻辑其实已经基本正确,但是提交依旧通过不了测试点时心态浮躁导致不能细心利用IDEL调试发现错误。所以得到的经验就是先测给出的测试用例,如果测试用例出现错误例如在某楼层无限循环或重复打印某一楼层状态,则运用增加提示信息打印后设置断点单步调试解决问题,逻辑错误用肉眼较难发现。
3. 改进建议
- 类与类关系设计并不清晰,没有的实现单一职责原则(SRP)。
- 大量使用if-else嵌套语句,从Kiviat图可知Depth(嵌套深度) 和 Complexity(圈复杂度)的最大值和平均值都远超正常水平,这样不仅违背职责单一还可能会造成1.可读性差2.单元测试覆盖率低3.修改易引发连锁错误,应当使用方法封装重复代码片段,优化逻辑以及不必要的讨论,使代码易于维护与扩展。
- 从Kiviat图中可知代码注释过少,应适当增加注释从而提高代码的可读性。
第二次 NCHU_单部电梯调度程序😊
-
题目需求
- 读入数据时要经过合法性检验过滤连续重复数据。
- 类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客请求类、队列类以及控制类。
1. 设计与分析
1.PowerDesigner类图
点击查看类图设计
设计解释
- 将方向与电梯状态设计成枚举类。
- 设计外部队列和内部队列为范型链表。
- 设计请求类与外部队列类关系为聚合,采用饿汉式单例模式将内外队列链表设置成请求类属性。
- 设计管理类实现电梯调度(类与类之间的联系)有效减小耦合度(不和陌生人说话🤐)。
- 采用正则表达式读取输入信息并且增加合法性检验逻辑。
2.SourceMontor报表
点击查看分析报告图示
- 由分析报告可知,在明确调整类间关系之后,Kiviat图中的Avg Complexity(平均圈复杂度)和Max Depth(最大嵌套深度)不降反升高,由此可见算法优化的空间依旧很大。仔细分析原因之后我发现是因为在遇到代码长度过长的问题时,我并没有静下心来优化代码的算法逻辑,而是简单粗暴的将没有用到的getter和setter和构造方法全部删除后勉强达标提交。所以在代码量又进一步提升的前提下我把没用到的方法全部删除导致了平均圈复杂度(平均圈复杂度)的进一步升高,而最大嵌套深度(Max Depth)是因为增加了类的设计。
点击查看方法具体复杂度
3.思路解释与分析
-
读取存入队列的基本逻辑和第一次电梯调试基本相同,增加了合法性校验逻辑即只需要判断新增加进来的数组和队列中最后一个比较。
-
本次的调度算法逻辑在主体上保持不变,但在类结构设计方面有了显著的变化。为了更好地实现程序的模块化和可维护性,新增了外部队列类、请求类和管理类。内部队列由于仅需存储整型数字,因此依旧以泛型链表的形式存在。
外部队列类作为请求类的一个属性被聚合其中,这种设计使得请求类能够更方便地管理与队列相关的数据和操作。而管理类则充当了 “中介” 的角色,它将各个类之间的关系进行了有效的连接和协调。例如,管理类负责处理请求类与外部队列类之间的数据交互,以及与电梯类之间的调度逻辑传递。通过这种类结构的设计,使得整个程序的逻辑更加清晰,各个模块之间的职责更加明确,有利于后续的代码维护和功能扩展。
2. 踩坑心得
- 踩坑:在开始本次作业时,由于贪图便捷,我直接沿用了第一次的算法逻辑,该逻辑中包含了大量的 if-else 语句来处理各种情况。随着作业的推进,为了满足题目要求,我不断地增加了许多类、构造函数、getter 和 setter 函数。然而,这种做法导致了提交代码的长度大幅增加,最终超过了题目所规定的 16kb 的限制。在意识到代码长度超标后,我没有及时考虑对算法进行优化,而是花费了大量时间在对代码进行删减上,试图通过删除一些自认为 “不必要” 的代码来满足长度要求。这种做法虽然最终勉强通过了测试点,但却带来了更严重的问题。通过代码分析工具发现,代码的 Avg Complexity(平均圈复杂度)从之前的 2.85 提升到了 3.02,Max Depth(最大嵌套深度)也从 3.8 提升到了 4.12。这表明代码的逻辑变得更加复杂,可读性和可维护性都大大降低。
- 心得:这次的经历让我深刻认识到,在面临算法可能需要调整的情况下,不能仅仅为了快速通过测试点而采取取巧的方式。走捷径虽然可能在短期内解决问题,但从长远来看,会给后续的开发工作带来诸多隐患。当发现代码存在问题时,应该沉下心来,认真分析问题的根源,从算法层面进行优化,而不是仅仅在表面上进行修改。例如,在本次作业中,如果我能够及时意识到原算法的局限性,对其进行重构,采用更合理的设计模式或算法逻辑,不仅可以避免代码长度过长的问题,还能提高代码的质量和性能。同时,这也提醒我在今后的编程工作中,要更加注重代码的质量和可维护性,养成良好的编程习惯,避免再次陷入类似的困境。
点击查看报错图片
3. 改进建议
- 在电梯调度算法的具体逻辑上还存在较大的优化空间,如重复代码片段的提取与封装。
- 虽满足了类与类之间的关系,但是在具体实现每个方法的功能是依旧没有做到单一职责原则(SRP)。
- 大量使用if-else嵌套语句,从Kiviat图可知Depth 和 Complexity的最大值和平均值都远超正常水平,这样不仅违背职责单一还可能会造成1.可读性差2.单元测试覆盖率低3.修改易引发连锁错误,应当使用方法封装重复代码片段,优化逻辑以及不必要的讨论,使代码易于维护与扩展。
- 从Kiviat图中可知代码注释过少,应适当增加注释从而提高代码的可读性。
第三次 NCHU_单部电梯调度程序😁
-
题目需求
- 对之前电梯调度程序再次进行迭代性设计,加入乘客类(Passenger),取消乘客请求类,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客类、队列类以及控制类,读入数据方式发生变化。
1. 设计与分析
1.PowerDesigner类图
点击查看类图设计
设计解释
- 将方向与电梯状态设计成枚举类。
- 设计乘客类存储目标楼层与当前楼层。
- 设计请求队列并且以饿汉式单例模式创建实例化对象。
- 设计管理类实现电梯调度(不和陌生人说话🤐)。
- 采用字符串自带方法判断分割读取数据。
2.SourceMontor报表
点击查看报告情况
- 由分析报告可知本人在前两次迭代的基础上进行了大量优化,基本实现了单一职责原则(SRP)。
- 本人对相应的代码进行部分注释之后Kiviat图中comment(代码注释)部分也从1.0提升到了4.7,Avg Complexity(平均圈复杂度)从2.51下降到2.42,Average Block Depth(平均嵌套深度)从3.02下降到1.71。
图示对比点击查看
点击查看方法具体复杂度
3.思路解释与分析
1.本人在第三次电梯调度程序中改用String中自带的contains方法进行读取存入。
2.在电梯调度上采用类与类关系基本不变,加入了乘客类。
3.在方法具体实现中将每一个功能板块拆开实现单一职责原则(SRP)。
2. 踩坑心得
- 踩坑
- 前期急于编码实现功能,未对方法功能进行细致划分,导致电梯状态管理模块中 “电梯方向切换” 和 “楼层停靠判断” 等逻辑混杂在一个方法中,该方法代码行数最终达 150 余行,增加调试难度与维护成本。
- 代码复杂度优化时,过度关注算法逻辑精简,忽视代码注释对复杂度评估的影响,虽整体平均圈复杂度在 2 - 3 之间,但因注释量过少未达优质代码标准。
- 心得:
- 深刻认识到编码前详细规划方法功能的重要性,将混杂方法拆分为 5 个独立方法后,模块代码结构更清晰,利于新增功能开发,长远可提升开发效率与代码质量。
- 意识到代码注释不仅有助于理解代码逻辑,更是代码质量的重要组成部分,补充 21 条详细注释后,提升了代码可读性、可维护性,成功降低整体平均圈复杂度至优质标准。
3. 改进建议
- 虽然复杂度基本在范围之内,但是核心算法的实现依旧有待优化与改进。
- 可以增加抽象类与接口从而更好的实现代码的可扩展性与维护性。
总结与提升
- 思维:通过本次迭代作业的练习,本人的思维有了一定的提升,掌握了对情况复杂的程序分类进行讨论的能力,如实现电梯调度算法时,使用适量if-else进行严谨讨论,还掌握了将复杂问题拆分为若干子问题进行解决,如将电梯调度分为求下一层,转化方向,移动,队列请求,入队,出队等等。
- 编码能力:通过本次迭代作业的练习,本人对java的基本语法有了一定的了解与掌握,掌握了类的基本创建方法,类与类关联,依赖,聚合即在类中创建另一个类并将它作为属性,也了解到饿汉式单例类的创建方法,这样就避免了一开始访问造成空指针索引的问题。虽然调用速度得到了提高,但是资源利用还有需优化。
- 心性:通过本次的迭代练习,本人心性也没有开始那么浮躁与追求速度,而是懂得了“欲先善其事,必先利其器”,编码过程中不能只追求速度,而应将要用到的数据结构,类图设计做好再进行编码。在进行迭代作业的过程中也不能只追求通过测试点,而应该抱着学习提高自己的能力去编码,这样在面临到代码路程中下一个技术壁垒时才能更加游刃有余。
建议
- 第一次迭代作业中题目并没有完全说清楚电梯运行情况,题目意思有点模糊导致学生可能会浪费较多时间在题目意思的理解和设计尝试的时间上,但是实际上方向都没有走对我认为这种将大量时间用在错误的设计上会有些许的不当,虽然能锻炼我们的心性但是也会打击某些同学导致后面再解释题目之后也不愿意尝试。相反让同学们理解对了题目意思再进行类设计的考虑与锻炼,我认为这样是更有意义的。
- 题目设置的分值可以从少到多实现递增这样就能引起同学门对于迭代难题的重视程度并且鼓励他们继续尝试后面的难题。
附录
点击查看main函数读取存入代码
Scanner sc = new Scanner(System.in);
int a, b,temp;
temp=0;
a = sc.nextInt();
b = sc.nextInt();
Elevator ele = new Elevator(a,b);
ele.setNowFloor(a);
sc.nextLine();
String s = sc.nextLine();
//String regex="<\\d+(,UP|,DOWN)?>";判断输入是否合法
String regex1 = "<(\\d+)(?:,(UP|DOWN))?>";
Pattern p1 = Pattern.compile(regex1);
while (!s.equalsIgnoreCase("end")) {
Matcher m1 = p1.matcher(s);
if (m1.find()) {
int floor = Integer.parseInt(m1.group(1));
if(floor>=a&&floor<=b)
{
String direction = m1.group(2);
if(direction==null){
ele.addin(floor);
}
else{
//System.out.println(ele.max);
if (direction.equals("UP")){
String s3="UP";
ele.addout(floor,s3);
}
else{
String s3="DOWN";
ele.addout(floor,s3);
}
}
}
}
s = sc.nextLine();
}
ele.index=2;
while(!ele.IQ.isEmpty()||!ele.OQ.isEmpty()){
ele.processRequests();
}
点击查看优化代码1
- 原代码1
private Integer getNextFloor() {
int target = 0;
boolean iqEmpty = queue.internalRequest.isEmpty();
boolean oqEmpty = queue.externalRequests.isEmpty();
if (iqEmpty && !oqEmpty) {
target = queue.externalRequests.getFirst().sourceFloor;
determineDirection(target);
flag = 2;
return target;
} else if (oqEmpty && !iqEmpty) {
target = queue.internalRequest.getFirst().destinationFloor;
determineDirection(target);
flag = 1;
return target;
} else if (!oqEmpty) {
int a = queue.internalRequest.getFirst().destinationFloor;
int b = queue.externalRequests.getFirst().sourceFloor;
determineDirection(target);
if (elevator.direction == Direction.UP) {
if (a >= elevator.curentFloor && b >= elevator.curentFloor) {
if (getClosest(a, b) == 1) {
flag = 1;
return a;
} else if (getClosest(a, b) == 2&&queue.externalRequests.getFirst().getDirection()==elevator.direction) {
flag=2;
return b;
} else if (getClosest(a, b) == 0||getClosest(a, b) == 2) {
flag = 1;
return a;
}
} else if (a >= elevator.curentFloor) {
flag = 1;
return a;
} else if (b >= elevator.curentFloor) {
flag = 2;
return b;
}
} else if (elevator.direction == Direction.DOWN) {
if (a <= elevator.curentFloor && b <= elevator.curentFloor) {
if (getClosest(a, b) == 1) {
flag = 1;
return a;
} else if (getClosest(a, b) == 2&&queue.externalRequests.getFirst().getDirection()==elevator.direction) {
flag=2;
return b;
} else if (getClosest(a, b) == 0||getClosest(a, b) == 2) {
flag = 1;
return a;
}
} else if (a > elevator.curentFloor && b <= elevator.curentFloor) {
flag = 2;
return b;
} else if (a <= elevator.curentFloor) {
flag = 1;
return a;
}
}
}
return 0;
}
- 优化1
private Integer getNextFloor() {
int target = 0;
boolean iqEmpty = queue.internalRequest.isEmpty();
boolean oqEmpty = queue.externalRequests.isEmpty();
//内部队列为空,外部队列不为空
if (iqEmpty && !oqEmpty) {
target = queue.externalRequests.getFirst().sourceFloor;
determineDirection(target);
flag = 2;
return target;
}//外部队列为空,内部队列不为空
else if (oqEmpty && !iqEmpty) {
target = queue.internalRequest.getFirst().destinationFloor;
determineDirection(target);
flag = 1;
return target;
}
//两个队列都不为空的情况
else if (!oqEmpty) {
int a = queue.internalRequest.getFirst().destinationFloor;
int b = queue.externalRequests.getFirst().sourceFloor;
determineDirection(target);
if (elevator.direction == Direction.UP) {
judge(a,b);
} else if (elevator.direction == Direction.DOWN) {
judge(a,b);
}
}
return 0;
}
private void judge(int a, int b){
flag=1;
if (getClosest(a, b) == 2&&queue.externalRequests.getFirst().getDirection()==elevator.direction) {
flag=2;
}
}
点击查看优化代码2
- 原代码2
private void move(int target) {
Direction d = elevator.direction;
if(d==Direction.UP){
while(target>elevator.curentFloor){
if(result==0)
System.out.printf("Current Floor: %d Direction: UP\n", elevator.curentFloor);
elevator.curentFloor++;
result=0;
}
if(result==0)
System.out.printf("Current Floor: %d Direction: UP\n", elevator.curentFloor);
} else if (d==Direction.DOWN) {
while(target< elevator.curentFloor){
if(result==0)
System.out.printf("Current Floor: %d Direction: DOWN\n", elevator.curentFloor);
elevator.curentFloor--;
result=0;
}
if(result==0)
System.out.printf("Current Floor: %d Direction: DOWN\n", elevator.curentFloor);
}
if(shouldstop(target)){
opendoor();
result=1;
}
}
- 优化2
private void move(int target) {
Direction d = elevator.direction;
if(d==Direction.UP) {
moveup(target);
}
else if (d==Direction.DOWN) {
movedown(target);
}
if(shouldstop(target)){
opendoor();
result=1;
}
}
private void movedown(int target){
//向下移动方法
Direction d = elevator.direction;
while(target< elevator.curentFloor){
printcurrentdown();
elevator.curentFloor--;
result=0;
}
printcurrentdown();
}
private void moveup(int target){
Direction d = elevator.direction;
//如果目标楼层层大于当前楼层则一直打印信息
while(target>elevator.curentFloor){
printcurrentup();
elevator.curentFloor++;
result=0;
}
printcurrentup();
}
点击查看合法性校验代码
public boolean checkout(int floor, String direction) {
if (!externalRequests.isEmpty()) {
return externalRequests.getLast().floor != floor || externalRequests.getLast().direction != Direction.valueOf(direction);
}
return true;
}
LOOK 调度算法(全称为 Elevator LOOK Algorithm)是一种动态双向扫描调度策略
核心逻辑实现步骤:
- 保持当前移动方向
- 响应同方向请求
- 方向无请求时反转
你在本次迭代作业遇到了哪些挑战?欢迎在评论区留下你的建议😀!感谢您的阅读。