结对项目:电梯调度
本博客用以记述完成整个软件工程基础大作业项目的完成过程。
项目完成人:Zhang Junrui,Feng Maike
一、准备阶段
时间预估表:
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 60 | 30 |
· Estimate | · 估计这个任务需要多少时间 | 60 | 30 |
Development | 开发 | 1100 | 1330 |
· Analysis | · 需求分析 (包括学习新技术) | 240 | 150+30 |
· Design Spec | · 生成设计文档 | 60 | 60 |
· Design Review | · 设计复审 (和同事审核设计文档) | 20 | 10 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 120 | 60 |
· Design | · 具体设计 | 120 | 60 |
· Coding | · 具体编码 | 360 | 720 |
· Code Review | · 代码复审 | 60 | 120 |
· Test | · 测试(自我测试,修改代码,提交修改) | 120 | 120 |
Reporting | 报告 | 240 | 210 |
· Test Report | · 测试报告 | 120 | 60 |
· Size Measurement | · 计算工作量 | 60 | 30 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 60 | 120 |
合计 | 1400 | 1570 |
选用语言:C++
运行环境:64bit Windows 10
需求分析:
本项目的需求分析通过一个类图来实现:
电梯和乘客类之间为多对多的关系,每个乘客可以选择多个电梯,每个电梯也可以承载多名乘客。电梯类中定义相应的调度函数作为模块的接口。
(详细的内容参考项目需求分析文档)
二、算法编程
1.类的定义
这部分,我根据需求分析的类图对于电梯类Elevator和乘客类Passenger进行了初步的定义。
电梯类:
class Elevator
{
public:
Elevator(int id,int dt,int ft,int mn,int mw,vector<int> sf) {};
~Elevator() {};
int GetPeopleCount();
int GetWeightCount();
int GetCurrentFloor();
int GetStatus();
void SetPeopleCount(int people);
void SetWeightCount(int weight);
void SetStatus(int status);
void SetCurrentFloor(int floor);
private:
bool ServeFloors[21];//服务楼层数组,服务为true,不服务为false
int DoorTime;
int FloorTime;//每上升一层所花费的时间
int MaxNum;
int MaxWeight;
int ID;
int peoplecount;
int weightcount;
int CurrentFloor;//假设电梯的初始状态都在-1层
int Status;//1表示上行,2表示下行
};
乘客类
class Passenger
{
public:
int Weight;
int Destination;
int CurrentFloor;
Passenger(int w,int d,int c) {};
~Passenger() = 0 {};
int GetDestination();
int GetCurrentFloor();
};
这里只是对于两个类进行了初步的定义,在之后的具体调用中还会对其进行进一步的修改与完善。
2.乘客行为规定
该部部分主要是通过规定乘客对于电梯的选择优先级分类,来界定乘客在各种情况下的行为。
电梯参数
电梯编号 | 服务楼层 | 乘客人数限制 | 重量限制 |
---|---|---|---|
1 | 所有楼层 | 10 | 800kg |
2 | 1-10层 | 10 | 800kg |
3 | -1,1-10层 | 20 | 1600kg |
4 | -1,1,11-20层 | 20 | 2000kg |
如果一位乘客需要从3层到达20层,他应该如何行动呢?这里有两种类型的行动方案可供其选择:
1、直接乘坐1号电梯
2、通过换乘其他电梯
Zhang认为,如果考虑换乘,会带来很多问题却不一定带来效率的提升。因此我们在这里简化过程,只考虑直达的情况。
Zhang在这里列出了乘客对于电梯的选择优先级。
类型 | 起始楼层 | 目标楼层 | 电梯号优先级 |
---|---|---|---|
1 | -1,1~10 | -1,1~10 | 3,2,1 |
2 | -1,1 | 11~20 | 4,1 |
3 | 11~20 | 11~20 | 4,1 |
4 | -1,1~10 | 11~20 | 1 |
说明:
1.在这种调度算法下,电梯不论有没有人,都在每一个有效楼层停。此时如果有任何一个优先级列表里的电梯到达乘客起始楼层且加上此乘客没有超重或超人数限制,那么乘客乘电梯。
2.此表表示乘客选择电梯的优先级。在一定的起始楼层和目标楼层的情况下,电梯号优先级表示乘客优先乘坐哪部电梯。比如起始楼层为-1,目标楼层为10,那么在几部电梯同时到达-1层时,乘客优先选择3号电梯,如果3号电梯满载或达到人数上限,那么选择2号电梯,以此类推。
3.此表中的情况并没有完全包含所有满足条件的方式。比如-1层到1层,可以4部电梯都可以选。但考虑到这种情况比较少和方便处理,故并入第一种情况处理。
4.优先级的考虑综合了能否到达和电梯容量两种因素。如果电梯的有效楼层不满足乘客需求,直接排除。如果满足,那么优先选择容量大的电梯。
3.实现一个基准调度算法
这一阶段实现的是“BUS”算法来进行电梯的调度。
“BUS”算法:
即公共汽车算法,是整个电梯调度算法中的最差情况(因此我们之后实现的算法的性能都要高于该算法)。将电梯当作公交车,从-1层一直到最高层(20层),每一层都停,并且开门,让乘客进出,然后关门,继续向上走。直到最高层,再向下。
算法设计思路:
每个楼层设置一个队列来管理该层的乘客,和request来获取乘客的上下行需求。电梯中设置数组PassengerDestination来记录电梯中乘客的目的地。电梯在每一层都停下并且进行判断电梯内是否有乘客需要下电梯、该楼层是否有乘客需要上电梯(电梯上行时只搭载上行乘客,电梯下行时只搭载下行的乘客)。乘客按照先下后上的顺序进出电梯,同时改变电梯内部的相应参数和该楼层的乘客队列。
该部分算法主要由Zhang实现,详细内容参考源代码。
4.实现自己的调度算法
我们这里选择实现的两种算法分别是SSTF和LOOK算法,SSTF算法由Zhang实现,这里不做讲述。
LOOK算法:
LOOK 算法是扫描算法(SCAN,也是第2阶段中的基准调度算法,只是不在每一层都停)的一种改进。对LOOK算法而言,电梯同样在最底层和最顶层之间运行。但当 LOOK 算法发现电梯所移动的方向上不再有请求时立即改变运行方向。
算法设计思路:
对于每层电梯停下时的操作与BUS一样,但是与之不同的是,电梯电梯不再在每一层都停下,而是在有乘客的同向运动申请或者是由电梯内乘客的下车申请时停下。
LOOK算法中的重点是电梯的变向问题,这里电梯在到达所有乘客的所在楼层c和目的楼层d的最值(max(max(c),max(d)))时变向。
while (e1.CurrentFloor <= highest) {
if (e1.PassengersDestination[e1.CurrentFloor] == 1 || e1.uprequest[e1.CurrentFloor] == 1) {
e1.WaitingForPassengers();
}
if (e1.CurrentFloor == highest) {
e1.Status = -1;
break;
}
e1.ElevatorMove();
}
但是,随着乘客数量的增加,会出现电梯一个往返无法将所有乘客运送完成的情况,而此时如果电梯仍然在之前的最值间运动,势必会导致算法的性能下降。故而,我决定使用动态的乘客所在楼层和目的楼层的最值来解决这个问题。(我的实现方法比较丑陋且暴力,应该有更优的方法)
//flag用于判断时求最大值还是最小值
int Get_est(int flag) {
queue <Passenger> tmpqueue1[21],tmpqueue2[21];
vector <int> vec;
for (int i = 0; i <= 20; i++) {
tmpqueue1[i] = UpQueue[i];
tmpqueue2[i] = DownQueue[i];
while (!tmpqueue1[i].empty()) {
vec.push_back(tmpqueue1[i].front().CurrentFloor);
vec.push_back(tmpqueue1[i].front().Destination);
tmpqueue1[i].pop();
}
while (!tmpqueue2[i].empty()) {
vec.push_back(tmpqueue2[i].front().CurrentFloor);
vec.push_back(tmpqueue2[i].front().Destination);
tmpqueue2[i].pop();
}
}
int result;
if (flag == 0) {
result = *min_element(vec.begin(), vec.end());
return result;
}
else {
result = *max_element(vec.begin(), vec.end());
return result;
}
}
int highest = e1.Get_est(1);
int lowest = e1.Get_est(0);
(这里有一个尚未解决的问题:min_element和max_element在有些编译器中会报错,这里我使用的时VS2019,就没有出现这个问题)
最后当电梯中的UpQueue和DownQueue均为空的时候电梯停止运行。
三、界面设计和代码性能测试
代码测试由Zhang完成,详情参考他的博客。
界面设计采用Qt进行(由于时第一次使用Qt,因此很多的操作还没有很好地掌握,故而界面设计得很简陋)
UI模块与其他模块之间通过调用类Elevator进行对接。Button Start用于控制程序的运行。4个电梯均用QLabel类表示,每当电梯得所在楼层发生变化时,相应的label利用坐标变换移动到相应的楼层所对应的位置。当电梯中没有乘客时,电梯为蓝色,当电梯中有乘客时,电梯为绿色,当电梯满载时,电梯为红色。每当电梯发生移动的时候都使用repaint函数进行窗口的刷新以实现电梯的动态运行。
四、总结
关于结对
由于是第一次参加结对项目,没有很好的找到个人在整个项目中的定位,也没有非常特定的合作方式。同时,因为疫情的原因导致的线上作业,我们只能通过微信视频等方式进行沟通(照片见附件),两人之间的交流和合作也有一定的不便。
在整个项目的过程中,队友体现出了很多的值得我学习的优点,比如对问题的分析能力强,善于自学,编程速度快,但是也有编程不很严谨的问题,这个问题我们在通过相互检查的方法将其适当地缩小了。
项目评价
总体上来讲,完成了项目的基本要求,但是由于是第一次进行结对编程,仍然造成了很大的问题:
1.由于需求分析阶段的分析不够严谨导致类中的接口在各个阶段的编程中不断地更改,导致代码地复用性很差。
2.为了调用方便,将大量的类的变量设为了public,从而牺牲了类的封装性。
3.因为第一次 接触Qt,导致Qt界面设计出现了很大的问题。
4.由于疫情和临时事件的影响整个项目时间安排失衡。
收获
1.第一次结对的经历让我逐渐掌握应该如何和队友协作编程,并在项目中找到自己的定位。
2.第一次使用Qt进行界面设计让我学到了很多关于Qt的东西。
3.逐渐熟练地掌握了使用git进行代码管理和博客的编写。