BUAA北航面向对象OO第二单元总结
OO第一单元总结
第五次作业
题目说明
本次作业需要模拟一个多线程实时电梯系统。
系统基于一个类似北京航空航天大学新主楼的大楼,大楼有 A,B,C,D,E五个座,每个楼座有对应的一台电梯,可以在楼座内 1-10 层之间运行。
系统从标准输入中输入请求信息,程序进行接收和处理,模拟电梯运行,将必要的运行信息通过输出接口进行输出。
具体而言,本次作业电梯系统具有的功能为:上下行,开关门,以及模拟乘客的进出。
电梯系统可以采用任意的调度策略,即在任意时刻,系统选择上下行动,是否在某层开关门,都可自定义,只要保证在电梯系统时间不超过系统时间上限的前提下将所有的乘客送至目的地即可。
电梯每上下运行一层、开关门的时间为固定值,仅在开关门窗口时间内允许乘客进出。
电梯系统默认初始在 A,B,C,D,E五个楼座的1层中各有一个电梯。
HW5基本思路
本次作业是多线程第一次作业,要求实现五栋楼中各自的一个电梯,不涉及电梯间的信息交流。本次作业使用了简单的生产者消费者模式。笔者设计了三个线程类,一是输入线程,二是调度器线程,三是电梯线程。线程之间的通信依赖于线程安全的请求队列(核心成员为LinkedList<PersonRequest>)和候乘表(核心成员为HashMap<Integer, LinkedList<PersonRequest>>),二者实现了请求分派队列的接口。
UML图
可以看出,似乎还是比较清晰的
时序图
InputHandler作为生产者发送请求,Scheduler作为消费者接受请求,又作为生产者发送请求,电梯作为消费者接受请求
调度器设计与线程交互
输入线程
与调度器线程共享一个请求队列,输入线程不断接受输入,如果没有输入就阻塞等待;如果有输入就将输入添加到请求队列中;输入结束就通知设置请求队列输入结束,并自身退出循环结束线程。
调度器线程
与输入线程共享一个请求队列,与电梯线程共享一个候乘表。调度器线程不断从请求队列中获取请求,如果获取到了请求,就根据请求的楼座将请求添加到对应的电梯的候乘表。当调度器线程与输入线程共享的请求队列被输入线程标记为输入结束,且请求队列为空时,将五个电梯线程的候乘表设置为工作结束,并退出循环,结束线程。
调度器只进行请求的接受的分配
电梯线程
与调度器线程共享一个候乘表,并拥有乘客队列。在电梯的run方法循环中
1.首先判断候乘表和乘客队列是否为空,若候乘表和乘客队列为空再判断是否候乘表工作结束,若候乘表工作结束则退出循环结束线程,否则电梯wait等待乘客。
2.接着根据策略类的判断设置电梯运行方向
3.再判断是否有乘客可以进入和是否有乘客需要离开,若有则开门并进行乘客离开和进入的操作(应当先离开再进入,否则可能出现拒载的情况)。
4.在电梯开门关门后再进行一次候乘表和乘客队列为空再判断是否候乘表工作结束的判断(可能出现乘客已经全部离开并且候乘表没有乘客的情况,若不判断,则电梯会到新的一层),即重复1、2步
5.根据策略类给出的方向进行电梯移动
调度策略
调度策略封装在策略类中,电梯拥有策略类属性。
电梯根据候乘表的状态进行调度,而不是由调度器进行调度
本次作业调度策略采用look,具体如下:
1.若电梯中有乘客要到达的楼层在当前的方向上的前面,则保持当前方向不变
2.若1不成立,则若候乘表中有乘客出发的楼层在当前的方向上的前面,则保持当前方向不变
3.若1,2都不成立,则电梯原地等待
注意:策略类只是给出电梯前进的方向,电梯允许乘客进来仅当电梯没满员且乘客的方向与电梯前进的方向相同。如果不考虑电梯与乘客方向的匹配,比如电梯在上行,有下行的乘客进来了,会导致整体用时增加。
PS:其实我感觉look策略实现起来比ALS好像更简单且整体性能优于ALS,不知道为啥课程组要设置ALS为基准策略,是为了鼓励同学上网查资料?
电梯行为
1.判断候乘表和乘客队列是否都为空
- 是的话判断是否候乘表工作结束
- 是的话电梯运行结束
- 否则等待
- 否则.根据策略类获得下一步的方向
3.乘客进出电梯
4.再次判断候乘表和乘客队列是否都为空(可能电梯出完了人导致乘客队列为空)
- 是的话判断是否候乘表工作结束
- 是的话电梯运行结束
- 否则等待
- 否则根据策略类获得下一步的方向
5.电梯根据方向到达下一层
第六次作业
题目说明
本次作业在第一次作业的基础上新增了ABCDE五个楼座的横向电梯,并支持在运行中新增加竖直和横向电梯的请求,同时可以一栋楼或者一层楼有多部电梯
HW6基本思路
第六次作业只是在第五次作业的基础上新增了横向电梯和新增竖直和横向电梯的请求,前者只需要对横向电梯新增加一个类,后者只需要在调度器中新增加对电梯请求的处理,二者处理起来比较简单。难点在于横向电梯的调度策略和多电梯的请求分配。前者我采用了横向look策略。
后者有两种处理方式,一种是课程组给的基准策略
在电梯的调度上,采用一种较为均衡的调度方式,例如 AA 座 77 层有5个乘客,3部电梯,那么第1个乘客分配给第1部电梯,第2个乘客分配给第2部电梯,第3个乘客分配给第3部电梯,第4个乘客分配给第1部电梯,第5个乘客分配给第2部电梯
另一种则是自由竞争
同一楼座或者楼层的电梯共享一个候乘表,当调度器把请求发送给某一层或者某一栋楼的候乘表后,所有电梯都能看到这个请求,然后根据自身的状态选择是否去竞争这个请求,虽然可能会出现陪跑的情况,但经过测试,性能还是比统一分配要高一点的。其实现实生活中也是这样,你坐电梯的时候把左右两个电梯都按了,哪个电梯先到你就进哪个。
其中自由竞争实现较为容易,于是我选择了自由竞争作为多电梯的调度策略。
UML图
竖直电梯和横向电梯拥有各自的候乘表类和策略类,其实可以让横向候乘表类和竖直候乘表类实现候乘表类接口,横向策略类和竖直策略类实现策略类接口,可是当时没有时间,就没有实现😣
时序图
从HW5的Main启动所有线程,变为了Main启动调度器和输入线程,调度器线程启动电梯线程,更利于增加电梯的请求
调度器设计与线程交互
线程交互部分与第一次作业保持一致,调度器方面新增了对新增电梯请求的处理,同时新增了横向电梯的候乘表
同时使用观察者模式,把调度器作为主题,电梯作为观察者,调度器把更新的信息(请求)推(push)给电梯。同时电梯可以注册成为观察者,也可以取消观察者的身份。前者利于第六次作业的新增电梯请求,后者方便扩展电梯取消的请求
调度策略
竖直电梯和第一次作业一样采用look策略
横向电梯采用横向look策略,具体如下:
1.电梯有乘客时方向不变,路上遇到乘客就接。
2.否则此时电梯乘客为空,判断当前方向后三座是否有同向的乘客,有的话就去接
3.否则查看前三座是否有乘客,有的话换方向去接乘客
4.如果都不满足,此时候乘表和乘客列表均为空,电梯等待
横向电梯行为
1.判断候乘表和乘客队列是否都为空
- 是的话判断是否候乘表工作结束
- 是的话电梯运行结束
- 否则等待
- 否则.根据策略类获得下一步的方向
3.乘客进出电梯
4.再次判断候乘表和乘客队列是否都为空(可能电梯出完了人导致乘客队列为空)
- 是的话判断是否候乘表工作结束
- 是的话电梯运行结束
- 否则等待
- 否则根据策略类获得下一步的方向
5.电梯根据方向到达下一层
在横向电梯根据策略类判断下一个方向时,总把横向电梯当前所在的楼座看成是五个楼座的中心(即如果电梯在C,则把楼座看成ABCDE,如果电梯在D,则把楼座看成BCDEA)
第七次作业
题目说明
本次作业在第二次作业的基础上对增加电梯的请求新增了速度,容量的设置,其中横向电梯还可以设置可开门楼座,同时乘客请求可以跨楼座请求,如从B3到C5,同时初始在一层有一个可以到达五个楼座的电梯
HW7基本思路
第七次作业是在第六次作业的基础上增加了对电梯速度,容量,横向电梯可达楼层的设置,同时运行乘客跨楼层请求。
前者的速度和容量很好处理,只需要在电梯类中增加speed和capacity两个属性,可达楼层的设置则涉及到了横向电梯的一些逻辑的改写。原先横向电梯判断候乘表为空是判断整个候乘表是否为空,现在则是判断候乘表里没有电梯能接送的乘客时候乘表为空(对于这个电梯来说)
乘客跨楼层请求有两种处理办法。
一种是静态分配,根据我的理解就是调度器在取得输入线程的请求时按照乘客请求的路线将其拆分成几个阶段,将不同阶段的请求视为独立的请求,然后把请求包装起来,发送给电梯。电梯将当前阶段的请求完成后,判断这个请求是否是最后一个阶段,不是的话再把请求送回给请求队列,被调度器重新分配。
一种是动态分配,我的理解是根据请求的目的楼层楼座和出发楼层楼座来进行包装设计中转目的楼层和目的楼座,比如一个请求从A座3层到B座2层,根据中转策略(比如官方的基准策略)可以将请求包装成从A座3层到A座1层的请求,然后调度器将请求分配给A座纵向电梯,电梯将包装后的请求运送到中转目的地(A座1层)以后,判断包装请求到达的楼座楼层是否和他真实的请求相同,不相同的话将该包装请求的出发楼层楼座设置为电梯当前的楼层楼座,然后将这个请求加入到请求输入队列中,并把这个请求从电梯本身的乘客队列中移除。然后调度器可以再次获得这个被包装的请求并进行包装分配。
两者的不同是动态分配每次都可以根据最新的情况规划请求,减少了静态分配的不及时性
需要注意的地方
第七次作业由于电梯需要把请求重新送回电梯,因此电梯也充当了生产者(以请求队列为托盘),故调度器的结束不能简单地以请求队列为空且输入线程结束来结束(前两次作业采用的),而是应当以加上所有请求的完成一起作为结束条件。
判断所有请求的完成的办法有两个
1.判断是否所有电梯都处于等待状态
2.当输入线程发出一个乘客请求时为请求队列内的请求数+1,电梯把乘客送到目的地时请求队列内的请求数-1,判断请求数是否为0
同时需要避免更新请求数或查询电梯状态造成的轮询
UML图
可以看出调度器实现了过多的功能,应该将调度器与请求的处理解耦,只进行获取请求->得到请求处理后的请求->发送请求的逻辑,应该新增一个类处理请求
时序图
电梯在本次作业中也充当生产者,将未完成的请求发送到请求队列,同时在完成请求时会通知请求队列将请求数减1
调度器设计与线程交互
调度器在前两次作业的基础上新增对根据乘客请求对中转楼层的选择,并将PersonRequest包装成PackedRequest,再把请求发送到电梯的候乘表
线程交互在前两次作业的基础上将电梯和输入线程共同作为生产者,电梯把未完成的请求发送到共享队列中,调度器从请求队列中接受的请求有三种,乘客请求,电梯请求,包装后的电梯请求
调度策略
竖直电梯保持原有策略
横向电梯判断是否有乘客时新增根据可达信息的判断,其余相同
换乘策略
基本按照官方基准思路,但是在基准思路的基础上增加了同一层的最短路换乘。即维护每一个楼层的楼座联通图,然后根据请求的出发楼座和终止楼座求每一层楼的楼座图最短路,看是否可达,如果可达且最短路径长度<=3,就按该楼层换乘,并且中转目的楼座设置为出发楼座和目的楼座的最短路的下一个顶点
比如第10层有AB和BC可达的电梯,有一个A到C的请求,按照基准策略这种请求只能下到1楼再上10楼,而我的策略可以在10楼A->B->C换乘
但似乎强测并没有这样的极端数据,所以这个优化也没有啥意义了🤣🤣🤣
同步块的设置和锁的选择
本次单元三次作业的锁全部采用ReentrantLock,基本形式为
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void method(){
lock.lock()
try {
while (waitingList.isEmpty() && !workFinished) {
condition.await();
}
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
这样保证了在方法的最后能释放锁,同时我感觉这样上锁的形式比synchronized
同步代码块和同步方法更直观,体现了临界区的含义,保证同时只能有一个线程访问临界区代码。
与同步块中处理语句之间的关系
当一个线程获取了lock.lock()的锁以后,其他线程访问这段代码尝试获取锁时会被阻塞;同时获取这段锁的线程可以通condition.await()释放锁并等待被唤醒,condition.signalAll()唤醒等待这把锁的线程,最后通过lock.unlock()释放锁。
虽然第二次作业时老师向我们推荐读写锁,可惜我没什么时间学,也就没有用(连续五周基物实验),第三次作业为了求稳也没有把ReentrantLock换成读写锁。
自动化评测和数据生成
总体上来说,第二单元的数据生成难度远小于第一单元,但评测机制造难度高于第一单元。第一单元的数据生成需要根据指导书的形式化表述进行递归的数据生成,并且数据需要达到一定强度。而第二单元只需要根据指导书的输入格式随机生成数据,不需要特意制造边界数据就有不错的强度。对于评测机,第一单元只需要利用subprocess调用IDEA就可以了然后代入数值比较结果就可以了,但第二单元需要实现完整的评测逻辑
第五次作业
数据生成
其实没啥好说的,就是根据指导书的输入格式,随机生成数据。可以根据调整随机数的范围生成不同强度的数据。其中设置乘客开始请求时间,而后每次随机增加乘客开始请求时间。每次随机生成beginBuilding, beginFloor, endBuilding, endFloor并确保 passengerId不同,将输入写到stdin.txt中
代码如下:
展开代码
import random
def getId(idList):
return passengerId
def getInput(curTime, passengerId, beginBuilding, beginFloor, endBuilding, endFloor):
return '['+str(curTime)+']'+ str(passengerId) + '-FROM-'\
+str(beginBuilding)+'-'+str(beginFloor)+'-TO-'+ str(endBuilding)+'-'+str(endFloor)
def gengerData():
randomNum = random.randint(40,70)
curTime = 0
Building = ['A', 'B', 'C', 'D', 'E']
idList = []
with open("stdin.txt",'w',encoding='utf-8') as f1:
for i in range(randomNum):
#gengeData
print(len(idList))
print()
print()
return randomNum
自动化评测
首先调用os.system('.\datainput_student_win64.exe | java -jar code.jar > stdout.txt')
根据stdin.txt的输入生成输出,然后再读取输出分析每一条输出进行逻辑和时间评测,逻辑评测利用状态机建模,时间评测利用课程组的发的时间检查包。由于我觉得应该不会有人轮询,有的话也过不了中测,于是就没写轮询评测机(结果在第二次作业发现一个人轮询出错的可能远大于逻辑出错的可能)
逻辑评测
即评测输出中有没有
1.电梯超载
2.电梯吃人
3.电梯跳层
4.先进人后开门
5.先出人后开门
6.电梯没关门就到下一层
7.电梯开门时重复开门
8.电梯关门时重复关门
9.乘客重复进入电梯
10.乘客重复离开电梯
11.电梯没人却仍有乘客
这些情况(待补充)
时间评测
保存每一个状态的时间,更新状态时判断两个状态之间的时间差值是否满足要求,比如两个ARRIVE之间要大于0.4s,OPEN和CLOSE之间的时间差值要大于0.4s等。并判断总用时是否与ALS基准策略相比超时。
代码如下
展开代码
import os
import data
import re
elevator = [[] for _ in range(5)]
elevatorState = ["ARRIVE", "ARRIVE", "ARRIVE", "ARRIVE", "ARRIVE"]
elevatorCurFloor = [1, 1, 1, 1, 1]
elevatorOpenTime = [0, 0, 0, 0, 0]
elevatorCloseTime = [0, 0, 0, 0, 0]
elevatorArriveTime = [0, 0, 0, 0, 0]
arrivedPassengerNum = 0
timeMsg = 0
def judgeOutput(output: str):
global timeMsg
idxTimeRight = output.index(']')
idxFirstarg = output.index('-')
actionType = output[idxTimeRight + 1: idxFirstarg]
timeMsg = float(output[1:idxTimeRight])
waMessage = ''
if actionType == 'ARRIVE':
inform = output[idxFirstarg + 1:].split('-')
waMessage = judgeARRIVE(inform[0], int(inform[1]), int(inform[2]), timeMsg)
elif actionType == 'OPEN':
inform = output[idxFirstarg + 1:].split('-')
waMessage = judgeOPEN(inform[0], int(inform[1]), int(inform[2]), timeMsg)
elif actionType == 'CLOSE':
inform = output[idxFirstarg + 1:].split('-')
waMessage = judgeCLOSE(inform[0], int(inform[1]), int(inform[2]), timeMsg)
elif actionType == 'IN':
inform = output[idxFirstarg + 1:].split('-')
waMessage = judgeIN(int(inform[0]), inform[1], int(inform[2]), int(inform[3]), timeMsg)
elif actionType == 'OUT':
inform = output[idxFirstarg + 1:].split('-')
waMessage = judgeOUT(int(inform[0]), inform[1], int(inform[2]), int(inform[3]), timeMsg)
return waMessage
def judgeARRIVE(building, floor, elevatorId, timeMsg):
def judgeOPEN(building, floor, elevatorId, timeMsg):
def judgeCLOSE(building, floor, elevatorId, timeMsg):
def judgeIN(passengerId, building, floor, elevatorId, timeMsg):
def judgeOUT(passengerId, building, floor, elevatorId, timeMsg):
def judgeEND(requets):
def judgeTime():
for i in range(1000):
#judge
第六次作业
数据生成
新增电梯请求的生成,注意不能在楼层没有电梯或初次有电梯一秒内就发出对该楼层的请求。我的做法是生成横向请求时判断该层是否有电梯,没有的话就去生成一个,并把当前时间+1s。
同时将乘客请求分为横向的和纵向的分别生成
代码如下:
展开代码
# #!/usr/bin/python3.9
# -*- coding: utf-8 -*-
# #
# Copyright (C) 2022 #
# @Time : 2022/4
# @Author : # @Email : sdjasjBUAA@gmail.com
# @File : $data.py
# @Author : BUAA-sdjasj
#
import random
MAX_BUILDING_ELEVATOR_NUM = 5
MAX_FLOOR_ELEVATOR_NUM = 3
Building = ['A', 'B', 'C', 'D', 'E']
idList = []
elevatorIdList = [1, 2, 3, 4, 5]
curTime = 0
floorElevatorTime = {}
buildingElevatorNum = {'A': 1, 'B':1, 'C':1, 'D':1, 'E':1}
floorElevatorNum = {}
personReq = 0
for i in range(1,11):
floorElevatorNum[i] = 0
for i in range(1,11):
floorElevatorTime[i] = 0
def getPersonId():
def getElevatorId():
def getBuildingInput():
def getFloorInput():
def addBuildingElevator(specBuild=None):
def addFloorElevator():
def gengerData():
global curTime
global personReq
global idList
global elevatorIdList
global floorElevatorNum
global buildingElevatorNum
global floorElevatorTime
idList = []
elevatorIdList = [1, 2, 3, 4, 5]
curTime = 1.1
floorElevatorTime = {}
buildingElevatorNum = {'A': 1, 'B': 1, 'C': 1, 'D': 1, 'E': 1}
floorElevatorNum = {}
personReq = 0
for i in range(1, 11):
floorElevatorNum[i] = 0
for i in range(1, 11):
floorElevatorTime[i] = 0
randomNum = random.randint(80, 90)
with open("stdin.txt", 'w', encoding='utf-8') as f1:
for _ in range(randomNum):
randomP = random.random()
if 0.9 <= randomP:
input = getBuildingInput()
elif 0.2 <= randomP < 0.9:
input = getFloorInput()
elif 0.1 <= randomP < 0.2:
input = addBuildingElevator()
else:
input = addFloorElevator()
f1.write(input)
print(input)
f1.write('\n')
print(personReq)
print()
print()
return personReq
自动化评测
在第五次作业的基础上新增了每个函数对横向电梯行为的评测,同时由于官方没有提供时间检测包,于是取消了时间评测,改为将时间写入文件,方便观测整体用时
同时新增了时间戳单调不减的检测
代码如下
展开代码
# #!/usr/bin/python3.9
# -*- coding: utf-8 -*-
# #
# Copyright (C) 2022 #
# @Time : 2022/4
# @Author : # @Email : sdjasjBUAA@gmail.com
# @File : $judge.py
# @Author : BUAA-sdjasj
#
import os
import data
buildingElevator = {}
for i in range(1, 6):
buildingElevator[i] = []
floorElevator = {}
elevatorState = {}
for i in range(1, 6):
elevatorState[i] = "ARRIVE"
elevatorCurFloor = {}
for i in range(1, 6):
elevatorCurFloor[i] = 1
elevatorCurBuilding = {}
for i in range(1, 6):
elevatorCurBuilding[i] = chr(ord('A') + i - 1)
elevatorOpenTime = {}
for i in range(1, 6):
elevatorOpenTime[i] = 0
elevatorCloseTime = {}
for i in range(1, 6):
elevatorCloseTime[i] = 0
elevatorArriveTime = {}
for i in range(6):
elevatorArriveTime[i] = 0
arrivedPassengerNum = 0
inPassengerNum = 0
timeMsg = 0
def addElevator(addArgList: list):
addType = addArgList[1]
if addType == 'building':
elif addType == 'floor':
def judgeOutput(output: str):
global timeMsg
idxTimeRight = output.index(']')
idxFirstarg = output.index('-')
actionType = output[idxTimeRight + 1: idxFirstarg]
if timeMsg > float(output[1:idxTimeRight]):
timeMsg = float(output[1:idxTimeRight])
return "is not incremental time " + ' [' + str(timeMsg) + ']'
else:
timeMsg = float(output[1:idxTimeRight])
waMessage = ''
if actionType == 'ARRIVE':
inform = output[idxFirstarg + 1:].split('-')
waMessage = judgeARRIVE(inform[0], int(inform[1]), int(inform[2]), timeMsg)
elif actionType == 'OPEN':
inform = output[idxFirstarg + 1:].split('-')
waMessage = judgeOPEN(inform[0], int(inform[1]), int(inform[2]), timeMsg)
elif actionType == 'CLOSE':
inform = output[idxFirstarg + 1:].split('-')
waMessage = judgeCLOSE(inform[0], int(inform[1]), int(inform[2]), timeMsg)
elif actionType == 'IN':
inform = output[idxFirstarg + 1:].split('-')
waMessage = judgeIN(int(inform[0]), inform[1], int(inform[2]), int(inform[3]), timeMsg)
elif actionType == 'OUT':
inform = output[idxFirstarg + 1:].split('-')
waMessage = judgeOUT(int(inform[0]), inform[1], int(inform[2]), int(inform[3]), timeMsg)
return waMessage
def judgeARRIVE(building, floor, elevatorId, timeMsg):
if floor >= 11:
return "floor > 10"
if floor <= 0:
return "floor < 1"
if elevatorId in buildingElevator.keys():
return ""
elif elevatorId in floorElevator.keys():
return ""
else:
return "no elevator " + str(elevatorId)
def judgeOPEN(building, floor, elevatorId, timeMsg):
return ""
def judgeCLOSE(building, floor, elevatorId, timeMsg):
return ""
def judgeIN(passengerId, building, floor, elevatorId, timeMsg):
global inPassengerNum
if floor >= 11:
return "floor > 10"
if floor <= 0:
return "floor < 1"
inPassengerNum+=1
if elevatorId in buildingElevator.keys():
elif elevatorId in floorElevator.keys():
else:
return "no elevator " + str(elevatorId)
def judgeOUT(passengerId, building, floor, elevatorId, timeMsg):
global arrivedPassengerNum
if floor >= 11:
return "floor > 10"
if floor <= 0:
return "floor < 1"
if elevatorId in buildingElevator.keys():
elif elevatorId in floorElevator.keys():
else:
return "no elevator " + str(elevatorId)
def judgeEND(requets):
def judgeTime():
global timeMsg
print(timeMsg)
for i in range(1000):
requestNum = data.gengerData()
os.system('.\datainput_student_win64.exe | java -jar code.jar > stdout.txt')
buildingElevator = {}
for i in range(1, 6):
buildingElevator[i] = []
floorElevator = {}
elevatorState = {}
for i in range(1, 6):
elevatorState[i] = "ARRIVE"
elevatorCurFloor = {}
for i in range(1, 6):
elevatorCurFloor[i] = 1
elevatorCurBuilding = {}
for i in range(1, 6):
elevatorCurBuilding[i] = chr(ord('A') + i - 1)
elevatorOpenTime = {}
for i in range(1, 6):
elevatorOpenTime[i] = 0
elevatorCloseTime = {}
for i in range(1, 6):
elevatorCloseTime[i] = 0
elevatorArriveTime = {}
for i in range(6):
elevatorArriveTime[i] = 0
arrivedPassengerNum = 0
inPassengerNum = 0
timeMsg = 0
flag = True
with open('stdin.txt', 'r', encoding='utf-8') as f:
while True:
stdinStr = f.readline()
if stdinStr == '\n' or stdinStr == "":
break
idxTimeRight = stdinStr.index(']')
alist = stdinStr[idxTimeRight + 1:].split('-')
if alist[0] == 'ADD':
addElevator(alist)
with open("stdout.txt", 'r', encoding='utf-8') as f:
while True:
output = f.readline()
if output == '\n' or output == "":
break
waMessage = judgeOutput(output)
if waMessage != "":
flag = False
with open("error3.txt", 'a', encoding='utf-8') as err:
err.write(waMessage)
err.write('\n')
print(waMessage)
waMessage = judgeEND(requets=requestNum)
if waMessage != "":
flag = False
with open("error3.txt", 'a', encoding='utf-8') as err:
err.write(waMessage)
err.write('\n')
print(waMessage)
judgeTime()
if not flag:
with open("error3.txt", 'a', encoding='utf-8') as err:
err.write('\nstdin:\n')
with open("stdin.txt", 'r', encoding='utf-8') as stdin:
while True:
stdintext = stdin.readline()
if len(stdintext) < 4:
break
err.write(stdintext)
with open("stdout.txt", 'r', encoding='utf-8') as out:
err.write('\nstdout:\n')
while True:
stdouttext = out.readline()
if len(stdouttext) < 4:
break
err.write(stdouttext)
err.write('\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n')
print("------------WA---------------")
else:
print("------------AC---------------")
第七次作业
数据生成
在第六次作业的基础上将横向和纵向的乘客请求合并为出发楼座目的楼座,起始楼层目的楼层二者只有一个能够相同的乘客请求,同时为电梯随机生成容量、速度和可达性数据
展开代码
# #!/usr/bin/python3.9
# -*- coding: utf-8 -*-
# #
# Copyright (C) 2022 #
# @Time : 2022/4
# @Author : # @Email : sdjasjBUAA@gmail.com
# @File : $data.py
# @Author : BUAA-sdjasj
#
import random
MAX_BUILDING_ELEVATOR_NUM = 3
MAX_FLOOR_ELEVATOR_NUM = 3
MAX_ELEVATOR_NUM = 20
curElevatorNum = 6
Building = ['A', 'B', 'C', 'D', 'E']
idList = []
elevatorIdList = [1, 2, 3, 4, 5, 6]
curTime = 0
floorElevatorTime = {}
buildingElevatorNum = {'A': 1, 'B':1, 'C':1, 'D':1, 'E':1}
floorElevatorNum = {}
personReq = 0
elevatorCapacity = [4, 6, 8]
elevatorSpeed = [0.2, 0.4, 0.6]
arrivedPassengerInfo = {}
ElevatorMsg = {}
passengerList = []
for i in range(1,5):
ElevatorMsg[i] = [8, 0.6]
ElevatorMsg[6] = [8, 0.6, 31]
for i in range(1,11):
floorElevatorNum[i] = 0
floorElevatorNum[1] = 1
for i in range(1,11):
floorElevatorTime[i] = 0
def getPersonId():
passengerId = random.randint(1, 1000)
while passengerId in idList:
passengerId = random.randint(1, 1000)
idList.append(passengerId)
return passengerId
def getElevatorId():
elevatorId = random.randint(7, 1000)
while elevatorId in elevatorIdList:
elevatorId = random.randint(7, 1000)
elevatorIdList.append(elevatorId)
return elevatorId
def getPersonRequest():
##根据相关法律法规和政策,部分内容未予显示
return '[' + str(curTime) + ']' + str(passengerId) + '-FROM-' \
+ str(beginBuilding) + '-' + str(beginFloor) + '-TO-' + str(endBuilding) + '-' + str(endFloor)
def addBuildingElevator(specBuild=None):
##根据相关法律法规和政策,部分内容未予显示
return '[' + str(curTime) + ']' + 'ADD-building-' + str(elevatorId) + '-' + str(beginBuilding) + '-' + str(capacity) + '-' + str(speed)
def addFloorElevator():
##根据相关法律法规和政策,部分内容未予显示
return '[' + str(curTime) + ']' + 'ADD-floor-' + str(elevatorId) + '-' + str(beginFloor) + '-' + str(capacity) + '-' + str(speed) + '-' + str(switchInfo)
def gengerData():
global curTime
global personReq
global idList
global elevatorIdList
global floorElevatorNum
global buildingElevatorNum
global floorElevatorTime
global curElevatorNum
global arrivedPassengerInfo
global ElevatorMsg
global passengerList
idList = []
elevatorIdList = [1, 2, 3, 4, 5]
curTime = 1.1
floorElevatorTime = {}
buildingElevatorNum = {'A': 1, 'B': 1, 'C': 1, 'D': 1, 'E': 1}
floorElevatorNum = {}
arrivedPassengerInfo = {}
ElevatorMsg = {}
personReq = 0
curElevatorNum = 5
arrivedPassengerInfo = {}
ElevatorMsg = {}
passengerList = []
for i in range(1, 6):
ElevatorMsg[i] = [8, 0.6]
ElevatorMsg[6] = [8, 0.6, 31]
for i in range(1, 11):
floorElevatorNum[i] = 0
floorElevatorNum[1] = 1
for i in range(1, 11):
floorElevatorTime[i] = 0
randomNum = random.randint(50, 50)
with open("stdin.txt", 'w', encoding='utf-8') as f1:
for _ in range(randomNum):
randomP = random.random()
if 0.3 <= randomP:
input = getPersonRequest()
elif 0.15 <= randomP < 0.3:
input = addBuildingElevator()
else:
input = addFloorElevator()
f1.write(input)
print(input)
f1.write('\n')
print(personReq)
print()
print()
print(curElevatorNum)
return [personReq,arrivedPassengerInfo, ElevatorMsg, passengerList]
自动化测试
由于第七次作业乘客可以多次换乘,于是乘客到达不能简单地在judgeOUT中判断,我的作法是生成数据时将每个乘客的目的楼层和目的楼座传入judge,在乘客OUT时判断是否已经到达目的楼层和目的楼座,是的话才将到达数+1,最后与乘客总请求数判断是否所有乘客到达目的地。同时data生成数据时将每个电梯的信息传给judge,方便根据每个电梯的信息判断是否有超速、超载、在不允许开门的地方开门等情况。
本次作业评测机新增了关于乘客先出后进的检测(前两次是电梯先开后关)
同时,在这次作业中,除了逻辑的评测,我还实现了轮询的评测,具体就是使用助教发的py检查CPU时间的代码加上我的数据生成器进行测试,虽然本地CPU时间与评测机的CPU时间差异较大,但如果轮询了,本地CPU时间仍然会在10s以上(正常0.几s)
逻辑评测代码如下
展开代码
# #!/usr/bin/python3.9
# -*- coding: utf-8 -*-
# #
# Copyright (C) 2022 #
# @Time : 2022/4
# @Author : # @Email : sdjasjBUAA@gmail.com
# @File : $judge.py
# @Author : BUAA-sdjasj
#
import os
import data
buildingElevator = {}
for i in range(1, 6):
buildingElevator[i] = []
floorElevator = {6: []}
elevatorState = {}
for i in range(1, 6):
elevatorState[i] = "ARRIVE"
elevatorState[6] = "ARRIVE"
elevatorCurFloor = {}
for i in range(1, 6):
elevatorCurFloor[i] = 1
elevatorCurBuilding = {}
for i in range(1, 6):
elevatorCurBuilding[i] = chr(ord('A') + i - 1)
elevatorCurBuilding[6] = 'A'
elevatorOpenTime = {}
for i in range(1, 7):
elevatorOpenTime[i] = 0
elevatorCloseTime = {}
for i in range(1, 7):
elevatorCloseTime[i] = 0
elevatorArriveTime = {}
for i in range(1, 7):
elevatorArriveTime[i] = 0
passengerArriveMsg = {}
elevatorMsg = {}
passengerList = []
passengerState = {}
arrivedPassengerNum = 0
inPassengerNum = 0
timeMsg = 0
def addElevator(addArgList: list):
addType = addArgList[1]
if addType == 'building':
##根据相关法律法规和政策,部分内容未予显示
elif addType == 'floor':
##根据相关法律法规和政策,部分内容未予显示
def judgeOutput(output: str):
global timeMsg
idxTimeRight = output.index(']')
idxFirstarg = output.index('-')
actionType = output[idxTimeRight + 1: idxFirstarg]
if timeMsg > float(output[1:idxTimeRight]):
timeMsg = float(output[1:idxTimeRight])
return "is not incremental time " + ' [' + str(timeMsg) + ']'
else:
timeMsg = float(output[1:idxTimeRight])
waMessage = ''
if actionType == 'ARRIVE':
inform = output[idxFirstarg + 1:].split('-')
waMessage = judgeARRIVE(inform[0], int(inform[1]), int(inform[2]), timeMsg)
elif actionType == 'OPEN':
inform = output[idxFirstarg + 1:].split('-')
waMessage = judgeOPEN(inform[0], int(inform[1]), int(inform[2]), timeMsg)
elif actionType == 'CLOSE':
inform = output[idxFirstarg + 1:].split('-')
waMessage = judgeCLOSE(inform[0], int(inform[1]), int(inform[2]), timeMsg)
elif actionType == 'IN':
inform = output[idxFirstarg + 1:].split('-')
waMessage = judgeIN(int(inform[0]), inform[1], int(inform[2]), int(inform[3]), timeMsg)
elif actionType == 'OUT':
inform = output[idxFirstarg + 1:].split('-')
waMessage = judgeOUT(int(inform[0]), inform[1], int(inform[2]), int(inform[3]), timeMsg)
return waMessage
def judgeARRIVE(building, floor, elevatorId, timeMsg):
if floor >= 11:
return "floor > 10"
if floor <= 0:
return "floor < 1"
if elevatorId in buildingElevator.keys():
##根据相关法律法规和政策,部分内容未予显示
return ""
elif elevatorId in floorElevator.keys():
##根据相关法律法规和政策,部分内容未予显示
return ""
else:
return "no elevator " + str(elevatorId)
def judgeOPEN(building, floor, elevatorId, timeMsg):
##根据相关法律法规和政策,部分内容未予显示
return ""
def judgeCLOSE(building, floor, elevatorId, timeMsg):
##根据相关法律法规和政策,部分内容未予显示
return ""
def judgeIN(passengerId, building, floor, elevatorId, timeMsg):
global inPassengerNum
if floor >= 11:
return "floor > 10"
if floor <= 0:
return "floor < 1"
if elevatorId in buildingElevator.keys():
##根据相关法律法规和政策,部分内容未予显示
return ""
elif elevatorId in floorElevator.keys():
##根据相关法律法规和政策,部分内容未予显示
return ""
else:
return "no elevator " + str(elevatorId)
def judgeOUT(passengerId, building, floor, elevatorId, timeMsg):
global arrivedPassengerNum
if floor >= 11:
return "floor > 10"
if floor <= 0:
return "floor < 1"
if elevatorId in buildingElevator.keys():
##根据相关法律法规和政策,部分内容未予显示
return ""
elif elevatorId in floorElevator.keys():
##根据相关法律法规和政策,部分内容未予显示
return ""
else:
return "no elevator " + str(elevatorId)
def judgeEND(requets):
if requets != arrivedPassengerNum:
s = str(requets - arrivedPassengerNum) + " people were eaten by elevator\nthey are\n"
for passenger in passengerList:
s += "passenger " + str(passenger) + '\n'
return s
for i in elevatorState.keys():
if elevatorState[i] == "ARRIVE" or elevatorState[i] == "CLOSE":
continue
else:
return "elevator " + str(i) + " is " + elevatorState[i] + " finally"
return ""
def judgeTime():
global timeMsg
print(timeMsg)
for i in range(1000):
requestNum, passengerArriveMsg, elevatorMsg, passengerList = data.gengerData()
os.system('.\datainput_student_win64.exe | java -jar code.jar > stdout.txt')
buildingElevator = {}
for i in range(1, 6):
buildingElevator[i] = []
floorElevator = {6: []}
elevatorState = {}
for i in range(1, 6):
elevatorState[i] = "ARRIVE"
elevatorState[6] = "ARRIVE"
elevatorCurFloor = {}
for i in range(1, 6):
elevatorCurFloor[i] = 1
elevatorCurBuilding = {}
for i in range(1, 6):
elevatorCurBuilding[i] = chr(ord('A') + i - 1)
elevatorCurBuilding[6] = 'A'
elevatorOpenTime = {}
for i in range(1, 7):
elevatorOpenTime[i] = 0
elevatorCloseTime = {}
for i in range(1, 7):
elevatorCloseTime[i] = 0
elevatorArriveTime = {}
for i in range(1, 7):
elevatorArriveTime[i] = 0
for passenger in passengerList:
passengerState[passenger] = "OUT"
arrivedPassengerNum = 0
timeMsg = 0
flag = True
with open('stdin.txt', 'r', encoding='utf-8') as f:
while True:
stdinStr = f.readline()
if stdinStr == '\n' or stdinStr == "":
break
idxTimeRight = stdinStr.index(']')
alist = stdinStr[idxTimeRight + 1:].split('-')
if alist[0] == 'ADD':
addElevator(alist)
with open("stdout.txt", 'r', encoding='utf-8') as f:
while True:
output = f.readline()
if output == '\n' or output == "":
break
waMessage = judgeOutput(output)
if waMessage != "":
flag = False
with open("error3.txt", 'a', encoding='utf-8') as err:
err.write(waMessage)
err.write('\n')
print(waMessage)
waMessage = judgeEND(requets=requestNum)
if waMessage != "":
flag = False
with open("error3.txt", 'a', encoding='utf-8') as err:
err.write(waMessage)
err.write('\n')
print(waMessage)
judgeTime()
if not flag:
with open("error3.txt", 'a', encoding='utf-8') as err:
err.write('\nstdin:\n')
with open("stdin.txt", 'r', encoding='utf-8') as stdin:
while True:
stdintext = stdin.readline()
if len(stdintext) < 4:
break
err.write(stdintext)
with open("stdout.txt", 'r', encoding='utf-8') as out:
err.write('\nstdout:\n')
while True:
stdouttext = out.readline()
if len(stdouttext) < 4:
break
err.write(stdouttext)
err.write('\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n')
print("------------WA---------------")
else:
print("------------AC---------------")
轮询评测代码如下(代码是助教发的应该不算违规吧?)
展开代码
import subprocess
import ctypes
import data
# must be shell=False
for i in range(1000):
data.gengerData()
input_process = subprocess.Popen(['datainput_student_win64.exe'], shell=False, stdout=subprocess.PIPE)
process = subprocess.Popen(['java', '-jar', 'code.jar'], shell=False, stdin=input_process.stdout)
input_process.wait()
process.wait()
handle = process._handle
creation_time = ctypes.c_ulonglong()
exit_time = ctypes.c_ulonglong()
kernel_time = ctypes.c_ulonglong()
user_time = ctypes.c_ulonglong()
rc = ctypes.windll.kernel32.GetProcessTimes(handle,
ctypes.byref(creation_time),
ctypes.byref(exit_time),
ctypes.byref(kernel_time),
ctypes.byref(user_time),
)
print((exit_time.value - creation_time.value) / 10000000)
print((kernel_time.value + user_time.value) / 10000000)
cputime = (kernel_time.value + user_time.value) / 10000000
with open('stdin.txt', 'r', encoding='utf-8') as f:
with open('stdtime.txt', 'a', encoding='utf-8') as t:
t.write(str((exit_time.value - creation_time.value) / 10000000))
t.write('\n')
t.write(str((kernel_time.value + user_time.value) / 10000000))
t.write('\n\n')
while True:
output = f.readline()
if output == '\n' or output == "":
break
t.write(output)
t.write('\n\n')
with open('time.txt', 'a', encoding='utf-8') as tt:
tt.write(str((exit_time.value - creation_time.value) / 10000000))
tt.write('\n')
tt.write(str((kernel_time.value + user_time.value) / 10000000))
tt.write('\n\n')
if cputime > 5.0:
with open('error.txt', 'a', encoding='utf-8') as ff:
ff.write(str(cputime))
ff.write('\n')
with open('stdin.txt', 'r', encoding='utf-8') as f:
while True:
output = f.readline()
if output == '\n' or output == "":
break
t.write(output)
t.write('\n\n')
分析自己程序的bug
由于自己搭建了评测机并每次作业至少用评测机跑一天以上的时间,故三次作业强测互测均没有被发现bug,三次强测得分分别为98.5788分、98.9975分、99分
在本地测试时
第五次作业评测机没有报错
第六次作业评测机显示我的电梯会floor > 10 或者floor <1,检查发现是电梯的状态模式转移有问题,改了一下就好了
第七次作业评测机没有报错
分析自己发现别人程序bug所采用的策略
没什么可说的,就是把别人代码下下来打包成jar,然后放评测机里跑,隔一段时间过来看看有没有error文件,因为实在是没有时间看别人代码。。
hack情况
第五次作业
hack数为21/63,(刀中的人/房间内被发现bug的人为4/4)
房间里有两个人输出不安全,没有按序输出,于是怎么hack怎么错
房间里有一个人并发修改异常,应该是没有做好线程安全
房间里有一个人没有把乘客全部送到目的楼层,应该也是线程不安全
第六次作业
hack数为5/140,(刀中的人/房间内被发现bug的人为4/5)
房间里有一个人会在电梯满时进人
房间里有两个人轮询,我只刀中一个,并且是意外刀中的,因为我没有检查轮询
房间里有一个人会重复ARRIVE,应该是电梯逻辑没写好
房间里有一个人会空指针异常,应该是线程安全没做好
第七次作业
hack数为11/35,(刀中的人/房间内被发现bug的人为5/5)
房间里有一个人会出现程序运行无法终止的情况,应该是死锁,在评测机理测一条卡一条,怎么测怎么错👀居然能过中测
房间里有两个人轮询(因为这次我写了轮询评测机,所以没有出现第二次作业hack不到轮询的情况)
房间里有一个人会出现横向电梯在不该开门的地方开门的情况,应该是逻辑没写好
房间里有一个人会并发修改异常,应该是线程安全没做好
本单元的测试策略与第一单元测试策略的差异之处
对我来说没有差异,都是评测机随机轰炸。。调试也是printf大法
毕竟高工的课实在是太多了,没有时间读别人代码`(>﹏<)′
心得体会
线程安全
总结起来就是要在合适的地方加锁,不要滥用加锁,确定好哪段代码是临界区(尽可能小,提高并发效率),将线程安全类与普通类分离开来。也不要滥用notifyall(可能导致轮询),也不要使用notify(可能死锁),并利用lock做好线程间的互斥访问和同步通信。
层次化设计
第一次作业采用了简单了生产者消费者模型和单例模式
第二次作用采用了生产者消费者模型、单例模式、观察者模式、静态工厂模式
第三次作业与第二次作业相同
整体的框架在第一次作业时已经建立,后续的作业只是在第一次作业的基础上迭代开发,虽然在做作业的过程中知道了别人更好的架构,但因为自己已经写完,并且由于没有时间,所以没有勇气去重构,便一路沿用上次作业的架构,导致类的耦合度有点高,并且是面向具体编程,也没有面向接口编程,希望下个单元能够改进
总结
这个月是OO的电梯月,也是我基物实验五连周之月😆再加上高工繁重的课程安排,我能写OO的时间只有每天的晚上,并且还要复习OS,所以时间实在是不够用,于是很多想做的事情都没来得及做,是充满了遗憾的一个月
-
多线程的设计模式只在第五次作业开始前看了两三章,后来就没时间看了。
-
搭建的评测机只能单线程测试,无法做到一个测试点并发几十个线程一起测试,无法做到一个测试点被多个jar用来测试,本来想学习python多线程的,可惜由于时间关系只能作罢
-
读写锁和一些Java多线程知识(CAS,内存屏障......)没有时间看
-
......
但是在遗憾中还是有一点令人喜悦的地方的,那就是我在第一单元后又成功在第二单元搭建了评测机,并且帮助我找出了一些bug,同时也帮助我的一些朋友找出了他们的bug,并且成功在互测中hack到人,成功实现"渡人渡己",也完成了我在计组时想要搭建评测机的梦想。
希望下单元能再接再厉