NCHU——有关电梯调度问题的BLOG

前言
经过了总计三次的题目集的训练,我的感受颇丰,在感慨获取了新的知识的同时也总结了此题目集的相关信息与情况。
1.知识点
在整个题目集中,体现最多的便是单一职责原则(Single Responsibility Principle,SRP),即一个类应该只有一个引起它变化的原因,也可以说:一个类应该只负责一项职责。除此之外还有类的设计与封装(将不同的功能封装在不同的类中),先进先出(FIFO)的数据结构。当然,在这些大知识点的背后还有小知识点的点缀,例如 ArrayList 的使用和正则表达式的运用,正是这大大小小的知识点的运用,才构建出三次题目集的层层迭代递进。
2.题量
正所谓“不识庐山真面目,只缘身在此山中”,初次看见此题目时我惊讶题量之大,认为难以战胜,实则不然,在细细研读题目后才发现其中涉及到的逻辑思考与编程的数量并没有多少,尤其是第二次和第三次的条件增加,乘客请求楼层数有误和乘客请求不合理的情况又或是乘客请求输入变动和对于外部请求,当电梯处理该请求之后(该请求出队)的修改,其对于整体代码的修改并没有很多,总的来说,题量的多少与否取决与我们的心态,认识的越深,纵使万般艰险,实则如履平地。
3.难度
对于此次的难度,总的来说便是“关关难过关关过”,学习的过程是痛苦的,尤其是初次驾到,第一此题目集中不论是ArrayList的初次使用还是电梯运行方向的逻辑理解,都给了我不少的挑战,尽管我拼尽全力,第一次的题目依旧无法战胜,但是“纵使困顿难行,亦当砥砺奋进”,在对于系统多次认真研读下,我顺利完成了余下的两次题目集。此三次题目集我认为难度最大的便是对于是否转向的逻辑设计,其余的困难便是ArrayList与类的结合使用,其让我对于不同类的数据调用理解有不小的挑战。但回到先前说的,若是理解了运行逻辑,后续的改进便简单了许多。
设计与分析
题目集5
设计一个电梯类,具体包含电梯的最大楼层数、最小楼层数(默认为1层)当前楼层、运行方向、运行状态,以及电梯内部乘客的请求队列和电梯外部楼层乘客的请求队列,其中,电梯外部请求队列需要区分上行和下行。
电梯运行规则如下:电梯默认停留在1层,状态为静止,当有乘客对电梯发起请求时(各楼层电梯外部乘客按下上行或者下行按钮或者电梯内部乘客按下想要到达的楼层数字按钮),电梯开始移动,当电梯向某个方向移动时,优先处理同方向的请求,当同方向的请求均被处理完毕然后再处理相反方向的请求。电梯运行过程中的状态包括停止、移动中、开门、关门等状态。当电梯停止时,如果有新的请求,就根据请求的方向或位置决定移动方向。电梯在运行到某一楼层时,检查当前是否有请求(访问电梯内请求队列和电梯外请求队列),然后据此决定移动方向。每次移动一个楼层,检查是否有需要停靠的请求,如果有,则开门,处理该楼层的请求,然后关门继续移动。
使用键盘模拟输入乘客的请求,此时要注意处理无效请求情况,例如无效楼层请求,比如超过大楼的最高或最低楼层。还需要考虑电梯的空闲状态,当没有请求时,电梯停留在当前楼层。
请编写一个Java程序,设计一个电梯类,包含状态管理、请求队列管理以及调度算法,并使用一些测试用例,模拟不同的请求顺序,观察电梯的行为是否符合预期,比如是否优先处理同方向的请求,是否在移动过程中处理顺路的请求等。为了降低编程难度,不考虑同时有多个乘客请求同时发生的情况,即采用串行处理乘客的请求方式(电梯只按照规则响应请求队列中当前的乘客请求,响应结束后再响应下一个请求),具体运行规则详见输入输出样例。

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

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

运行到某一楼层(不需要停留开门),输出一行文本:
Current Floor: 楼层数 Direction: 方向
运行到某一楼层(需要停留开门)输出两行文本:
Open Door # Floor 楼层数
Close Door
输入样例:
在这里给出一组输入。例如:

1
20
❤️,UP>
<5>
<6,DOWN>
<7>
<3>
end
输出样例:
在这里给出相应的输出。例如:

Current Floor: 1 Direction: UP
Current Floor: 2 Direction: UP
Current Floor: 3 Direction: UP
Open Door # Floor 3
Close Door
Current Floor: 4 Direction: UP
Current Floor: 5 Direction: UP
Open Door # Floor 5
Close Door
Current Floor: 6 Direction: UP
Current Floor: 7 Direction: UP
Open Door # Floor 7
Close Door
Current Floor: 6 Direction: DOWN
Open Door # Floor 6
Close Door
Current Floor: 5 Direction: DOWN
Current Floor: 4 Direction: DOWN
Current Floor: 3 Direction: DOWN
Open Door # Floor 3
Close Door
思考
鉴于这是第一次遇到此类题目,我对于题目逻辑的分析显得十分无措,对于类的构建也是十分混乱,再加上要使用先前很少使用的ArrayList,所以第一次的练习结果以大败而归结束,但鉴于不甘心,我于第二次的练习中将其完成,我将此题的分析如下写出:
类图:
由于后续老师提供了参考类图,我便模仿其进行创作

复杂度分析:
行数(Lines):393 代码行数较多,一定程度上反映了功能实现的复杂性
语句数(Statements):250 语句数量较多意味着代码实现的逻辑较为复杂
分支占比:(% Branches)20.0% ,表示代码中分支语句(如if - else、switch等)占比较高,说明代码中存在较多条件判断逻辑
调用数:(Calls):129 ,说明代码中方法调用比较频繁。频繁的方法调用本身不一定是问题,但结合较多的语句数和分支占比,可能意味着方法之间的调用关系较为复杂。
注释占比(% Comments):3.3% ,注释占比非常低,过少的注释会使其他开发人员(甚至是代码作者自己在一段时间后)难以快速读懂代码,不利于代码的维护和团队协作。
类数(Classes):7 ,表明代码中定义了 7 个类,一定程度上实现了面向对象编程中类的封装,但类的数量与代码行数、语句数对比来看,可能类的职责划分还不够精细,需要进一步审视是否遵循单一职责原则等面向对象设计原则。
平均每个类的方法数(Methods/Class):5.14 ,平均每个类的方法数不算少,可能存在类的职责不够单一的情况,导致一个类承担了过多功能。
平均每个方法的语句数(Avg Stmts/Method):4.61 ,平均每个方法的语句数不算特别多,但结合整体较大的语句数,可能存在部分方法语句数远高于平均值的情况,需要检查是否存在方法功能过于复杂、代码冗余等问题。
最大复杂度(Max Complexity):18 ,说明项目中存在复杂度较高的方法。高复杂度的方法往往难以理解、测试和维护,可通过拆分方法、提取重复代码等方式降低复杂度。
最大嵌套深度(Max Depth):5 ,意味着代码中存在嵌套层次较深的情况,可能是多层if - else嵌套或者循环嵌套等,这会使代码的执行逻辑难以跟踪,应尽量减少代码的嵌套深度,优化控制流结构。
平均嵌套深度(Avg Depth):2.24 ,平均嵌套深度尚可,但结合最大嵌套深度来看,仍有优化空间。
平均复杂度(Avg Complexity):2.61 ,平均复杂度不算太高,但由于存在最大复杂度为 18 的情况,说明代码复杂度分布不均匀,需要对高复杂度方法进行重点优化。


Kiviat Graph(雷达图)
雷达图展示了多个代码度量指标:% Comments(注释占比) 、Methods/Class(平均每个类的方法数) 、Avg Stmts/Method(平均每个方法的语句数) 、Max Complexity(最大复杂度) 、Max Depth(最大嵌套深度) 、Avg Depth(平均嵌套深度) 、Avg Complexity(平均复杂度) 。从图中可以直观看到各项指标的数值情况及相对关系,比如Max Complexity(最大复杂度)指标数值相对较高,偏离中心较远,说明存在复杂度较高的方法。
Block Histogram(柱状图)
横坐标表示每个代码块中的语句数量区间(0 - 1、1 - 2、2 - 3 等) ,纵坐标表示对应区间的代码块数量。从图中可以看出,语句数量在 1 - 2、2 - 3 区间的代码块数量较多,也存在部分语句数量较多(如 4 - 5、5 - 6 等区间)的代码块,反映了代码块语句数量的分布情况 。
题目集6
对之前电梯调度程序进行迭代性设计,目的为解决电梯类职责过多的问题,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客请求类、队列类以及控制类,具体设计可参考如下类图。
电梯迭代1类图.png
电梯运行规则与前阶段单类设计相同,但要处理如下情况:

乘客请求楼层数有误,具体为高于最高楼层数或低于最低楼层数,处理方法:程序自动忽略此类输入,继续执行
乘客请求不合理,具体为输入时出现连续的相同请求,例如<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
输入样例1:
在这里给出一组输入。例如:

1
20
❤️,UP>
<5>
<6,DOWN>
<7>
<3>
end
输出样例1:
在这里给出相应的输出。例如:

Current Floor: 1 Direction: UP
Current Floor: 2 Direction: UP
Current Floor: 3 Direction: UP
Open Door # Floor 3
Close Door
Current Floor: 4 Direction: UP
Current Floor: 5 Direction: UP
Open Door # Floor 5
Close Door
Current Floor: 6 Direction: UP
Current Floor: 7 Direction: UP
Open Door # Floor 7
Close Door
Current Floor: 6 Direction: DOWN
Open Door # Floor 6
Close Door
Current Floor: 5 Direction: DOWN
Current Floor: 4 Direction: DOWN
Current Floor: 3 Direction: DOWN
Open Door # Floor 3
Close Door
输入样例2:
在这里给出一组输入。例如:

1
20
❤️,UP>
❤️,UP>
<5>
<5>
<5>
<6,DOWN>
<7>
<7>
<3>
<22,DOWN>
<5,DOWN>
<30>
END
输出样例2:
在这里给出相应的输出。例如:

Current Floor: 1 Direction: UP
Current Floor: 2 Direction: UP
Current Floor: 3 Direction: UP
Open Door # Floor 3
Close Door
Current Floor: 4 Direction: UP
Current Floor: 5 Direction: UP
Open Door # Floor 5
Close Door
Current Floor: 6 Direction: UP
Current Floor: 7 Direction: UP
Open Door # Floor 7
Close Door
Current Floor: 6 Direction: DOWN
Open Door # Floor 6
Close Door
Current Floor: 5 Direction: DOWN
Open Door # Floor 5
Close Door
Current Floor: 4 Direction: DOWN
Current Floor: 3 Direction: DOWN
Open Door # Floor 3
Close Door
由于上一次的电梯问题是在本次题目集中完成,代码相似度基本一样,以下继续提供更加细致的代码分析,由于篇幅有限,在此不一一赘述。



这段电梯调度程序代码在复杂度方面表现出多维度特征。从规模看,文件、行数及语句数较多,代码有一定体量;结构上,分支占比和调用数反映出条件判断与方法调用频繁,逻辑较复杂。注释稀少不利于理解。类与方法层面,类数不算少但平均方法数、最大复杂度、最大嵌套深度等指标显示,存在部分方法或类职责不够单一、逻辑复杂的情况,整体复杂度有优化空间,可从拆分功能、精简逻辑和增加注释等方面改进。
题目集7
对之前电梯调度程序再次进行迭代性设计,加入乘客类(Passenger),取消乘客请求类,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客类、队列类以及控制类,具体设计可参考如下类图。
类图.png
电梯运行规则与前阶段相同,但有如下变动情况:

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

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

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

运行到某一楼层(不需要停留开门),输出一行文本:
Current Floor: 楼层数 Direction: 方向
运行到某一楼层(需要停留开门)输出两行文本:
Open Door # Floor 楼层数
Close Door
输入样例1:
在这里给出一组输入。例如:

1
20
<5,4>
<5>
<7>
end
输出样例1:
在这里给出相应的输出。例如:

Current Floor: 1 Direction: UP
Current Floor: 2 Direction: UP
Current Floor: 3 Direction: UP
Current Floor: 4 Direction: UP
Current Floor: 5 Direction: UP
Open Door # Floor 5
Close Door
Current Floor: 6 Direction: UP
Current Floor: 7 Direction: UP
Open Door # Floor 7
Close Door
Current Floor: 6 Direction: DOWN
Current Floor: 5 Direction: DOWN
Open Door # Floor 5
Close Door
Current Floor: 4 Direction: DOWN
Open Door # Floor 4
Close Door
输入样例2:
在这里给出一组输入。例如:

1
20
<5,9>
<8>
<9,3>
<4>
<2>
end
输出样例2:
在这里给出相应的输出。例如:

Current Floor: 1 Direction: UP
Current Floor: 2 Direction: UP
Current Floor: 3 Direction: UP
Current Floor: 4 Direction: UP
Current Floor: 5 Direction: UP
Open Door # Floor 5
Close Door
Current Floor: 6 Direction: UP
Current Floor: 7 Direction: UP
Current Floor: 8 Direction: UP
Open Door # Floor 8
Close Door
Current Floor: 9 Direction: UP
Open Door # Floor 9
Close Door
Current Floor: 8 Direction: DOWN
Current Floor: 7 Direction: DOWN
Current Floor: 6 Direction: DOWN
Current Floor: 5 Direction: DOWN
Current Floor: 4 Direction: DOWN
Open Door # Floor 4
Close Door
Current Floor: 3 Direction: DOWN
Current Floor: 2 Direction: DOWN
Open Door # Floor 2
Close Door
Current Floor: 3 Direction: UP
Current Floor: 4 Direction: UP
Current Floor: 5 Direction: UP
Current Floor: 6 Direction: UP
Current Floor: 7 Direction: UP
Current Floor: 8 Direction: UP
Current Floor: 9 Direction: UP
Open Door # Floor 9
Close Door
Current Floor: 8 Direction: DOWN
Current Floor: 7 Direction: DOWN
Current Floor: 6 Direction: DOWN
Current Floor: 5 Direction: DOWN
Current Floor: 4 Direction: DOWN
Current Floor: 3 Direction: DOWN
Open Door # Floor 3
Close Door
加上前两次的经验,此次练习就比较顺利了,虽然目的没有完全达成,但已然符合整体目标,以下是代码结构分析
类图(参考于提供样例):

复杂度:
Files(文件数):值为 1 ,说明整个项目仅由一个源文件构成。
Lines(行数):395 行,代码行数较多。较多的行数意味着代码实现的功能丰富。
Statements(语句数):248 条,语句数量较多反映了代码逻辑的复杂性。
% Branches(分支占比):20.6% ,表明代码中分支语句(如if - else、switch等)占比较高。较多的分支会使代码执行路径复杂,降低可读性和可测试性。
Calls(调用数):132 ,说明代码中方法调用频繁。
% Comments(注释占比):3.0% ,注释占比极低。
Classes(类数):7 ,定义了 7 个类,一定程度上体现了面向对象的封装思想。
Methods/Class(平均每个类的方法数):5.14 ,平均每个类有 5.14 个方法,说明类的功能可能相对复杂。
Avg Stmts/Method(平均每个方法的语句数):4.56 ,平均每个方法的语句数不算特别多,但结合整体语句数较多的情况,可能存在部分方法语句数远超平均值。
Max Complexity(最大复杂度):18 ,表明项目中存在复杂度较高的方法。
Max Depth(最大嵌套深度):6 ,意味着代码中存在嵌套层次较深的情况,可能是多层if - else嵌套或循环嵌套等。
Avg Depth(平均嵌套深度):2.28 ,平均嵌套深度尚可
Avg Complexity(平均复杂度):2.69 ,平均复杂度不算高



三次解题过程中的心得
这三次的解题可谓是跌宕起伏,从第一次因不了解题目而失败的挫败感再到第二次成功解题的喜悦感最后到第三次解题的释怀感,第一次解题中因为在老师给予的建议中发觉要使用自己完全不熟悉的方法,当时的我十分的无力,在网络中略微了解后便去实际应用,结果是相当惨烈的,不断出现的编译错误叫我无从下手,直到截至时间的最后一秒我都在尽力修改错误,最终还是未完成任务,首次出战变大败而归,这使我的信心大减,但是俗话说”从哪里跌倒就从哪里战起来“,不一会儿我便投身于第二次的解题。吸取了第一次解题的教训,我在网络上更加细致的学习了自己不擅长的部分,并与同学进行了运行逻辑的请教,虽说过程还是十分艰难,但还是应了那句话”锲而不舍,金石可镂“,我最终还是解决了该问题,但是没有时间庆祝,紧接下来的便是第三次练习。鉴于前两次的练习,我对于第三次练习变得更加谨慎,老师虽说第三次的练习只有部分修改,但是依旧有部分难度,由于要求修改输入形式,对代码的部分修改也是十分费劲的,经常会遗漏掉一些未修改的部分,虽然比较麻烦但我还是完成了大致的修改,但我所碰到的问题并非在此,而是测试中的一个小测试点,我无论如何都无法通过该测试点,但我并未灰心,我假想了许多可能并将可能的问题一一修复,结果依旧错误直至截止时间,虽然没有得到满分,但我没有任何伤心的心情,因为我在过程中我已经充分的运用了自己所学的知识,未达到满分只是我还没想到问题所在,这更是告诫我,学习软件工程的路途道阻且长,我们需要更加的自信和坚持,可能结果并不是最好的,但我们为此努力过并获得了一定的成效,与我而言,这已经足够了。
踩坑心得
此次的练习,完全过了一次,和不完全过了一次外加完全没过一次,对于踩坑我只能提供我所遇见并解决的。
1.在第二次的练习中出现了方向转向错误的情况,例如当外部和内部分别为<6,DOWN><7>且当前运行向上时,电梯在向上经过6层时停止,此问题便是对方向处理的不完全,原逻辑是在开门并查重重复逻辑时判断一次方向,此时的方向会因<6,DOWN>而改变一次,进而误认为此时同方向请求,解决方式也很简易,只需在循环进入下一次运动时再次判别一次方向即可。
部分代码实现所示 ExternalRequest firstExternal = externalRequests.isEmpty() ? null : externalRequests.getFirst();
Integer firstInternal = internalRequests.isEmpty() ? null : internalRequests.getFirst();
determineDirection(); //一次
move();
currentFloor = elevator.getCurrentFloor();
if (shouldStop(currentFloor)) {
openDoors();
getSame();
if (firstExternal != null&&currentFloor == firstExternal.getFloor()) {
elevator.setDirection(firstExternal.getDirection());
}
removeRequests(currentFloor);
determineDirection(); //两次
}
2.在第三次练习中出现了请求分别为<5,4><5>时,在到5楼时将两请求全部删除,此问题的出现便是因为移除函数的功能重复,因为先前没考虑如此情况,所以移除函数逻辑有漏洞,只需在完成一个请求后立即返回循环即可。
部分代码实现所示
if (internalRequests.isEmpty() && !externalRequests.isEmpty() && currentFloor == externalRequests.getFirst().getFloor()) {
externalRequests.pollFirst();
}
return; //返回
}
if (!externalRequests.isEmpty() && currentFloor == externalRequests.getFirst().getFloor()) {
externalRequests.pollFirst();
return; //返回
}
3,在第三次练习中出现了请求分别为<5,4><5>时,对于是否删除外部还是内部请求发生分歧,原因是在上述改进中就简单的返回,并未就哪个先删除进行判断,只需在过程函数进行增添逻辑即可
部分代码实现所示
else if (!externalRequests.isEmpty() && currentFloor == externalRequests.getFirst().getFloor()) {
ExternalRequest req = externalRequests.getFirst();
removeRequests(currentFloor);
if (elevator.isValidFloor(req.getFloor2())) { //判断
queue.addInternalRequest(req.getFloor2());
}
}
改进建议
由于这三次练习是迭代完成的,我就对第三次给出见解,正如先前的分析,代码整体复杂度高并且不利于理解,我认为可以在之后的练习中加入更多的注释以便后期的测试和优化,对于代码尝试使用更为方便的逻辑,减少else-if的过度使用,防止后期维护困难。优化 determineDirection 方法:该方法的复杂度较高,可以通过提取公共逻辑、减少嵌套来优化。在 Main 类中,对用户输入的验证可以进一步完善,并且添加异常处理机制。
总结
在这三次的作业中,让我对类和对象的使用更加的深刻,也了解到更多的对象引用方法,其同时让我知道自学的重要性,若此次练习仅依靠自已现有的知识则完全无法解决这些问题,Java的真实面貌也渐渐揭露,她不像c一样较为公式,她的灵活性让我叹为观止,我认为在今后的学习中是完全无法忽略其的,更别说轻视了,我也需要做好心里准备,更加重视之后的每一次练习,同样,一个正常的心态也很重要,不为物喜,不以己悲,做不到就是做不到,不必为此而影响之后的心情,做好自己便可。
对于课程和题目的建议:
在今后的题目集中,老师可以多加几个测试点,不然可能会出现旱的旱死,涝的涝死,使该学科的学习变得十分极端,同时,在作业结束后也可以公布测试点的内容,方便同学后续的订正和理解,也便于该学科的进一步理解。

posted @ 2025-04-20 16:33  tsomrenni  阅读(41)  评论(0)    收藏  举报