题目集5-7电梯调度问题的BLOG
一、 前言
这三次的电梯调度迭代问题,虽然在做的过程中发生了诸多问题,但是我通过与老师和同学的激烈讨论,也是都成功做对了,在这个过程中我认识到了自己的不足,包括类设计思考方式,以及算法问题等等,这次作业涉及的问题有面向对象编程(OPP)、枚举类型、数据结构、输入输出处理、条件判断与循环结构、方法设计与调用、算法设计等,是一个非常全面的题目。
二、 设计与分析
题目集05:
原题:
设计一个电梯类,具体包含电梯的最大楼层数、最小楼层数(默认为1层)当前楼层、运行方向、运行状态,以及电梯内部乘客的请求队列和电梯外部楼层乘客的请求队列,其中,电梯外部请求队列需要区分上行和下行。
电梯运行规则如下:电梯默认停留在1层,状态为静止,当有乘客对电梯发起请求时(各楼层电梯外部乘客按下上行或者下行按钮或者电梯内部乘客按下想要到达的楼层数字按钮),电梯开始移动,当电梯向某个方向移动时,优先处理同方向的请求,当同方向的请求均被处理完毕然后再处理相反方向的请求。电梯运行过程中的状态包括停止、移动中、开门、关门等状态。当电梯停止时,如果有新的请求,就根据请求的方向或位置决定移动方向。电梯在运行到某一楼层时,检查当前是否有请求(访问电梯内请求队列和电梯外请求队列),然后据此决定移动方向。每次移动一个楼层,检查是否有需要停靠的请求,如果有,则开门,处理该楼层的请求,然后关门继续移动。
使用键盘模拟输入乘客的请求,此时要注意处理无效请求情况,例如无效楼层请求,比如超过大楼的最高或最低楼层。还需要考虑电梯的空闲状态,当没有请求时,电梯停留在当前楼层。
请编写一个Java程序,设计一个电梯类,包含状态管理、请求队列管理以及调度算法,并使用一些测试用例,模拟不同的请求顺序,观察电梯的行为是否符合预期,比如是否优先处理同方向的请求,是否在移动过程中处理顺路的请求等。为了降低编程难度,不考虑同时有多个乘客请求同时发生的情况,即采用串行处理乘客的请求方式(电梯只按照规则响应请求队列中当前的乘客请求,响应结束后再响应下一个请求),具体运行规则详见输入输出样例。
输入格式:
第一行输入最小电梯楼层数。
第二行输入最大电梯楼层数。
从第三行开始每行输入代表一个乘客请求。
电梯内乘客请求格式:<楼层数>
电梯外乘客请求格式:<乘客所在楼层数,乘梯方向>,其中,乘梯方向用UP代表上行,用DOWN代表下行(UP、DOWN必须大写)。
当输入“end”时代表输入结束(end不区分大小写)。
输出格式:
模拟电梯的运行过程,输出方式如下:
运行到某一楼层(不需要停留开门),输出一行文本:
Current Floor: 楼层数 Direction: 方向
运行到某一楼层(需要停留开门)输出两行文本:
Open Door # Floor 楼层数
Close Door
第一次接触这种类型的题目,我确实感到很难,看完题目后,我就像一个无头苍蝇一样不知道如何开写,索性从算法开始写,最后在补充其他的功能。
这么做完题目,我的函数并没有遵循单一职责的原则,而且算法也是及其不完善的,那么我的算法就交给题目集06再进行交代
代码类图如下:
使用SourceMonitor分析得到:
通过上图可以知道:
总行数 (Lines):205
有效语句数 (Statements):148
分支语句百分比 (Percent Branch Statements):22.3%
函数调用语句数 (Method Call Statements):96
注释比例 (Percent Lines with Comments):1.5%
类数 (Classes and Interfaces):3
方法数/类 (Methods per Class):2.67
方法平均语句数 (Avg. Statements per Method):15.63
最复杂方法 (Most Complex Method):Elevator.print()
最大复杂度 (Maximum Complexity):19
最大嵌套深度 (Maximum Block Depth):5
- 心得:问题主要表现为注释过少,算法的设计也有缺陷,类设计也不符合规范,导致我的第二次作业几乎算是重写的一遍。
题目集06:与题目集05不同的是多了一个电梯指令的查重。
第二次的电梯作业老师发了类设计的方法,帮助我更好的进行类设计,包括外部请求类、请求队列类、控制类等等。 - Main类:与上一次题目集电梯题目的Main类的设计相似,先用正则表达式解析每行用户所输入的数据然后按他们的种类的不同分为电梯内部请求和电梯外部请求,分别加入到RequestQueue类的匹配请求队列当中去。
- Elevator类:包含电梯可达的楼层范围、当前所在楼层、楼层合法性检验、电梯的运行方向和运行状态,用于Controller类的电梯运行逻辑分析。
- ExterRequest类:主要用来存储电梯外部请求的楼层与方向,用于RequestQueue类当中添加外部请求,使添加或使用外部电梯请求的逻辑更清晰。
- RequestQueue类:包括电梯内部请求和电梯外部请求的两个队列,以及添加请求、获得当前请求和检验请求是否重复方法,用于Controller类当中的电梯运行方向分析和判断。
- Controller类:关联Elevator类和RequestQueue类,主要用来执行电梯的运行方向及状态判断、控制电梯的移动、完成请求队列中的请求和打印当前电梯的运行状态和楼层。
- 枚举:用于描述电梯的三种运行方向以及电梯的三种不同的运行状态,方便后续的逻辑运行。
- 运行算法:先将内部请求和外部请求通过正则表达式输入队列,先判断电梯实际方向,再输出初始层数,进入循环(条件:内部请求和外部请求均不为空),判断下一层楼,改变运行方向,运行电梯,输出运行状态,最后删除请求。
- 判断下一层楼:判断(内部为空)直接输出外部头请求,判断(外部为空)直接输出内部请求,否则:定义一个外部方向exdirection为当前楼层到外部头请求的方向,定义一个内部方向indirection为当前楼层到内部头请求的方向,定义一个外部方向exdirection1为外部请求的方向,判断1(indirection与电梯方向相同且exdirection与电梯方向相同):判断(exdirection1与电梯方向相同):输出绝对值小的楼层。否则:输出内部请求的楼层。判断2(indirection与电梯方向相同且exdirection与电梯方向不同):输出内部请求楼层。判断3(indirection与电梯方向不同且exdirection与电梯方向相同):输出外部请求楼层。判断4:创建一个与电梯方向相反的方向,判断(该方向与exdirection1相同):输出请求楼层与当前楼层绝对值小的楼层。否则:输出内部请求。
- 删除请求:判断1(外部请求为空):删除内部请求。判断2(内部请求为空):删除外部请求。否则:{判断1(输入楼层与外部头请求相等且输入楼层与内部头请求不相等):删除外部头请求。判断2(输入楼层与外部头请求相等且电梯方向与外部请求方向相同):删除外部头请求。判断3(内部头请求楼层与输入楼层相同):删除内部头请求。}
代码类图如下:
使用SourceMonitor分析得到:
通过上图可以知道:
总行数 (Lines):366
有效语句数 (Statements):233
分支语句百分比 (Percent Branch Statements):24.0%
函数调用语句数 (Method Call Statements):106
注释比例 (Percent Lines with Comments):2.7%
类数 (Classes and Interfaces):7
方法数/类 (Methods per Class):5.00
方法平均语句数 (Avg. Statements per Method):4.97
最复杂方法 (Most Complex Method):Main.main()
最大复杂度 (Maximum Complexity):24
最大嵌套深度 (Maximum Block Depth): 7 - 分析:
1.代码的注释还是太少:注释量从一开始的1.5到现在的2.7,提升的很少。
2.添加了更多的类,让逻辑更加清晰了,并且符合java的函数设计基本原则。 - 心得:
要养写代码注释的习惯,尤其是复杂度逻辑部分。但相比上次题目集已经有了很大的改进了 - 题目集07:与06的不同是,把外部请求变为<所在层数,目标层数>,那么我们只需要稍微修改一下正则表达式,将内部请求序列进行修改,在每次删除电梯外部请求前,把目标楼层加入内部请求就可以了。
代码类图如下:
使用SourceMonitor分析得到:
通过上图可以知道:
总行数 (Lines):349
有效语句数 (Statements):229
分支语句百分比 (Percent Branch Statements):21.8%
函数调用语句数 (Method Call Statements):103
注释比例 (Percent Lines with Comments):0.9%
类数 (Classes and Interfaces):7
方法数/类 (Methods per Class):5.00
方法平均语句数 (Avg. Statements per Method):4.86
最复杂方法 (Most Complex Method):Controller.getNextFloor()
最大复杂度 (Maximum Complexity):25
最大嵌套深度 (Maximum Block Depth):7 - 总结:这次的代码质量和上一次的差不多。只是简单的增加了一个当外部请求要被处理时,把目标楼层加入到内部队列的尾部这个逻辑以及在输入的时候变动一些格式,电梯的主要运行逻辑方法的复杂度和深度依旧很高。证明我的电梯调度代码仍然需要改进。
三、踩坑心得
1. 对于电梯调度算法的错误理解:第一次看到这个题目,没有好好的理解意思,就任凭自己想象开始了,然后就做了1天的无用功,我以后一定要好好理解题目。
2. 电梯运行的逻辑错误:在输出楼层的最后一个判断语句内,忘记将楼层的方向改为反方向就进行比较,让我一直错误,但是找了很长时间错误没找到,最后还是和室友一起讨论时对逻辑进行梳理才找到问题所在。
四、改进建议
1. 代码结构与可读性
注释:代码中虽然有一些逻辑,但缺乏必要的注释,尤其是复杂的逻辑部分,如 getNextFloor 方法。添加详细的注释可以提高代码的可读性。
变量命名:部分变量命名不够清晰,例如 a、i、j、k 等,使用更具描述性的变量名可以让代码更易理解。
方法拆分:一些方法的逻辑较为复杂,如 processRequests 和 getNextFloor,可以将其拆分成更小的方法,提高代码的可维护性。
2. 性能优化
请求处理:在处理请求时,每次都遍历请求队列来获取下一个楼层,效率较低。可以使用更高效的数据结构或算法来优化请求处理过程。
重复计算:在 getNextFloor 方法中,有一些重复的计算,如 queue.externalRequests.get(0).getFloor() 和 queue.externalRequests.get(0).getFloor1(),可以将其结果缓存起来,避免重复计算。