一、前言
本次pta单部电梯调度程序分为三次,其中后两次作业是在前一次作业的基础之上进行迭代改进,因此,第一次作业就显得尤为重要,且万事开头难,光是将这个题目电梯的运行逻辑搞清楚要耗费不少时间,而且这个电梯LOOK算法变种与日常生活中的电梯很不一样,了解其中运行逻辑在开始时相当困难,在编写代码时也遇见了许多困难,比如怎么对输入的这串字符串进行处理,这些数字,字母,<>怎么去进行分割提取,为此学习了正则表达式,List等相关知识,还有写的过程中发现一些地方的逻辑错误或者是情况考虑少了等等。而第二、三次都是添加了一些要求也就是对第一次的代码进行迭代并用面向对象设计要求去改进完善代码以及完善算法,处理更多特殊情况之类的。三次题目层序渐进,环环相扣,让我边写边学收获了很多的新知识。
二、设计与分析
第一次电梯题目的设计与分析
题目要求:
设计一个电梯类,包含最大楼层数、最小楼层数(默认为1层)当前楼层、运行方向、运行状态,以及电梯内部乘客请求队列和外部请求队列,其中,电梯外部请求队列区分上下行。运行规则:电梯默认在1层,状态静止,有乘客发起请求时(各楼层电梯外部乘客按下上行或下行按钮或内部乘客按下想要到达的楼层数字按钮),电梯开始移动,当电梯向某个方向移动时,优先处理同方向请求,当同方向请求均被处理完毕再处理相反方向的请求。电梯运行过程中的状态包括停止、移动中、开门、关门等状态。当电梯停止时,如果有新请求,就根据请求方向或位置决定移动方向。电梯在运行到某一楼层时,检查当前是否有请求(访问电梯内请求队列和电梯外请求队列),然后据此决定移动方向。每次移动一个楼层,检查是否有需要停靠的请求,如果有,则开门,处理该楼层的请求,然后关门继续移动。还要注意处理无效请求情况,比如超过大楼的最高或最低楼层。
设计类图:
其中main类使用正则表达式来正确读取每行输入当中所需的数据,然后按数据的形式来进行外部或者内部的请求的加入。Elevator类用来控制电梯的运行,和方向的确定,以及打印电梯当前所在的楼层、状态和运行方向。由于还不会枚举,所以用的-1,0,1来表示电梯的方向向上,静止还是向下。
Souce Monitor分析结果:
这一次的代码写的很糟糕,因为可能C语言的设计思维还没有完全转变过来,再就是题目难度的忽然升高理解电梯运行逻辑等等所花时间太多了,编写代码的时间就少了很多。
点击查看代码
import java.util.*;
public class Main
{
static class Elevator
{
// 1,0,-1分别表示向上静止和向下
private int UP = 1;
private int IDLE = 0;
private int DOWN = -1;
private int minFloor;
private int maxFloor;
private int currentFloor;
private int direction; // 这个指的是当前方向
private LinkedList<Integer> innerQueue = new LinkedList<>(); //又用LinkedList看看时间能否减少
private LinkedList<Integer> outerUpQueue = new LinkedList<>();
private LinkedList<Integer> outerDownQueue = new LinkedList<>();
public Elevator(int min, int max)
{
minFloor = min;
maxFloor = max;
currentFloor = 1; // 初始在1层
direction = IDLE;
}
// 用于添加内部的请求
void addInner(int floor)
{
if (floor < minFloor || floor > maxFloor)
return;
innerQueue.add(floor);
}
// 添加外部请求
void addExternalQueue(int floor, String dir)
{
if (floor < minFloor || floor > maxFloor)
return;
if ("UP".equals(dir))
{
outerUpQueue.add(floor);
} else
{
outerDownQueue.add(floor);
}
}
private boolean hasRequest()
{ // 这个这个方法是说有没有请求<没有考虑要不要开门>
return!innerQueue.isEmpty() ||!outerUpQueue.isEmpty() ||!outerDownQueue.isEmpty();
}
// dianti的运行
void runFloor()
{ setInitialDir();
System.out.println("Current Floor: " + currentFloor + " Direction: " + getDirName());
while (hasRequest()||direction != IDLE)
{ // 如果有请求的话
// 处理当前楼层的请求
boolean needOpen = processFloor();
if (needOpen)
{ // 安装需要的输出格式输出
System.out.println("Current Floor: " + currentFloor + " Direction: " + getDirName());
System.out.println("Open Door # Floor " + currentFloor);
System.out.println("Close Door");
} else if (direction != IDLE)
{
System.out.println("Current Floor: " + currentFloor + " Direction: " + getDirName());
}
// 判断要不要转变方向
if (needReverse())
{
reverseDirection();
}
setInitialDir();
moveFloor();
} // 直到没有了请求
}
// 用于删除数组第一个元素
private void deleteFirst(LinkedList<Integer> list) {
if (!list.isEmpty()) {
list.removeFirst();
}
}
// 处理当前楼层请求(只检查队列第一个元素)
private boolean processFloor()
{
boolean open = false;
// 处理内部队列
if (!innerQueue.isEmpty() && innerQueue.peekFirst() == currentFloor)
{
deleteFirst(innerQueue);
open = true;
}
// 处理外部队列(根据方向)
LinkedList<Integer> externalQueue;
if (direction == UP)
{
externalQueue = outerUpQueue;
} else
{
externalQueue = outerDownQueue;
}
if (!externalQueue.isEmpty() && externalQueue.get(0) == currentFloor)
{
if (direction == UP)
{
deleteFirst(outerUpQueue);
} else
{
deleteFirst(outerDownQueue);
}
open = true;
}
return open;
}
// 判断是否需要转变电梯的方向
private boolean needReverse()
{
if (direction == IDLE)
return false;
// 检查当前方向是否还有请求
boolean hasRequest = false;
if (direction == UP)
{ // 如果方向向上
if (haveQueue(innerQueue, true) || haveQueue(outerUpQueue, true))
{ // 如果有内部向上或者外部向上的请求
hasRequest = true;
}
} else
{
if (haveQueue(innerQueue, false) || haveQueue(outerDownQueue, false))
{
hasRequest = true;
}
}
return!hasRequest;
}
// 检查队列中是否有<需要处理>的请求,这个方法是说有没有要处理的请求
private boolean haveQueue(LinkedList<Integer> queue, boolean isUp)
{
for (int floor : queue)
{ // forEach循环
if (isUp && floor > currentFloor)
{ // 如果电梯向上并且有大于当前楼梯的请求
return true;
}
if (!isUp && floor < currentFloor)
{ // 如果电梯向下并且有小于当前楼梯的请求
return true;
}
}
return false; // 其他情况都是false
}
// 电梯移动一楼
private void moveFloor()
{
if (direction == UP)
{
if (currentFloor < maxFloor)
{ // 如果电梯方向向上并且小于最大楼层
currentFloor++;
}
} else if (direction == DOWN)
{
if (currentFloor > minFloor)
{ // 如果电梯方向向下并且大于最小楼层的华
currentFloor--;
}
} else
{
setInitialDir();
}
}
// 用于设置初始方向
private void setInitialDir()
{
int firstReq = getFirstRequest();
if (firstReq > currentFloor)
{
direction = UP;
} else if (firstReq < currentFloor)
{
direction = DOWN;
}
else direction = IDLE;
}
// 获取第一个请求的楼层
private int getFirstRequest()
{
if (!innerQueue.isEmpty())
return innerQueue.getFirst();
if (!outerUpQueue.isEmpty())
return outerUpQueue.getFirst();
if (!outerDownQueue.isEmpty())
return outerDownQueue.getFirst();
return currentFloor; // 无请求时保持当前层
}
// 调转方向
private void reverseDirection()
{
if (direction == UP)
{
direction = DOWN;
} else if (direction == DOWN)
{
direction = UP;
}
// 如果调转后没有请求,则静止
if (!haveQueue(innerQueue, direction == UP) &&!haveQueue(innerQueue, direction == DOWN) &&
!haveQueue(outerUpQueue, direction == UP) &&!haveQueue(outerUpQueue, direction == DOWN))
{
// 如果各种请求都没有了的华
direction = IDLE;
}
}
// 将方向转换为字符串!差点忘了
private String getDirName()
{
if (direction == UP)
return "UP";
if (direction == DOWN)
return "DOWN";
return "IDLE";
}
}
public static void main(String[] args)
{
Scanner input = new Scanner(System.in);
int min = input.nextInt();
int max = input.nextInt();
input.nextLine(); // 读取换行符
Elevator elevator = new Elevator(min, max);
while (true)
{
String line = input.nextLine().trim();
if (line.equalsIgnoreCase("end"))
break;
line = line.replaceAll("[<>\\s]", ""); // 去掉<>和空格
if (line.contains(","))
{ // 如果包含,那就说明是外部的请求
String[] parts = line.split(",");
int floor = Integer.parseInt(parts[0]);
String dir = parts[1].toUpperCase();
elevator.addExternalQueue(floor, dir);
} else
{ // 否则就是内部的请求
int floor = Integer.parseInt(line);
elevator.addInner(floor);
}
}
elevator.runFloor();
}
}
第二次电梯题目的设计与分析
题目要求:
新增:乘客请求楼层数有误,具体为高于最高楼层数或低于最低楼层数,处理方法:程序自动忽略此类输入,继续执行。乘客请求不合理,具体为输入时出现连续的相同请求,例如<3><3><3>或者<5,DOWN><5,DOWN>,处理方法:程序自动忽略相同的多余输入,继续执行,例如<3><3><3>过滤为<3>,要分成多个类,并且要遵循单一职责原则(SRP)。
设计类图:
根据题目所给类图提示进行设计Main类用正则表达式解析输入数据分为电梯内部请求和电梯外部请求,分别加入到RequestQueue类的匹配请求队列当中。Elevator类包含电梯楼层范围、当前楼层、楼层合法性检验、电梯的运行方向和运行状态,用于Controller类的电梯运行逻辑。ExterRequest类用来存储电梯外部请求的楼层与方向。RequestQueue类包括电梯内部外部请求两个队列,以及添加请求、获得当前请求和检验请求是否重复方法,用于Controller类当中的电梯运行方向分析和判断。Controller类用来执行电梯的运行方向及状态判断、控制电梯的移动、完成请求队列中的请求和打印当前电梯的运行状态和楼层。
Souce Monitor分析结果:
这一次通过给出的java类图对思路有很大帮助,就像把一个复杂的问题分成多个小部分使其简单化,同时对问题出现错误在哪可以更加容易的找到。这次的代码写虽然还是存在一些问题,但是以经有了很大的进步,通过与其他同学的测试结果比对,我们都发现了一些问题,有时候问题出错很简单但是不报错,这种不小心写错导致的逻辑上的错误很难发现,再就是仍然是存在某种情况下电梯没有停下,这个当时来不及改,也不知道是到底哪里出了问题,一直被困在这里。
点击查看代码
import java.util.LinkedList;
import java.util.Scanner;
import java.util.Collections;
import java.util.List;
enum Direction {
UP, DOWN, IDLE
}
class ExternalRequest {
final int floor;
final Direction direction;
ExternalRequest(int floor, Direction direction) {
this.floor = floor;
this.direction = direction;
}
}
class RequestQueue {
private final LinkedList<Integer> internal = new LinkedList<>();
private final LinkedList<ExternalRequest> external = new LinkedList<>();
void addInternal(int floor) {
if (internal.isEmpty() || internal.getLast() != floor) {
internal.add(floor);
}
}
void addExternal(int floor, Direction direction) {
ExternalRequest newReq = new ExternalRequest(floor, direction);
if (external.isEmpty() || !isEqual(external.getLast(), newReq)) {
external.add(newReq);
}
}
private boolean isEqual(ExternalRequest a, ExternalRequest b) {
return a.floor == b.floor && a.direction == b.direction;
}
Integer peekInternal() { return internal.peek(); }
ExternalRequest peekExternal() { return external.peek(); }
void removeInternal() { internal.poll(); }
void removeExternal() { external.poll(); }
boolean isEmpty() { return internal.isEmpty() && external.isEmpty(); }
// 新增访问方法
List<Integer> getInternalRequests() {
return new LinkedList<>(internal); // 返回副本
}
List<ExternalRequest> getExternalRequests() {
return new LinkedList<>(external); // 返回副本
}
}
class Elevator {
int currentFloor;
Direction direction = Direction.IDLE;
final int minFloor;
final int maxFloor;
Elevator(int minFloor, int maxFloor) {
this.minFloor = minFloor;
this.maxFloor = maxFloor;
this.currentFloor = minFloor;
}
boolean isValidFloor(int floor) {
return floor >= minFloor && floor <= maxFloor;
}
void moveStep() {
if (direction == Direction.UP) currentFloor++;
else if (direction == Direction.DOWN) currentFloor--;
}
}
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int min = Integer.parseInt(scanner.nextLine());
int max = Integer.parseInt(scanner.nextLine());
Elevator elevator = new Elevator(min, max);
RequestQueue queue = new RequestQueue();
// 处理输入
String line;
while (!(line = scanner.nextLine()).equalsIgnoreCase("end")) {
line = line.trim().toUpperCase();
if (line.matches("<\\d+>")) {
int floor = Integer.parseInt(line.replaceAll("[<>]", ""));
if (elevator.isValidFloor(floor)) queue.addInternal(floor);
} else if (line.matches("<\\d+,\\s*(UP|DOWN)>")) {
String[] parts = line.replaceAll("[<>]", "").split(",");
int floor = Integer.parseInt(parts[0].trim());
Direction dir = Direction.valueOf(parts[1].trim());
if (elevator.isValidFloor(floor)) queue.addExternal(floor, dir);
}
}
// 电梯运行逻辑
while (!queue.isEmpty()) {
Integer M = queue.peekInternal();
ExternalRequest P = queue.peekExternal();
int N = elevator.currentFloor;
// 方向判定
if (elevator.direction == Direction.IDLE) {
if (M != null) {
elevator.direction = M > N ? Direction.UP : Direction.DOWN;
} else if (P != null) {
elevator.direction = P.floor > N ? Direction.UP : Direction.DOWN;
} else {
break;
}
System.out.printf("Current Floor: %d Direction: %s\n", N, elevator.direction);
}
// 执行移动
elevator.moveStep();
N = elevator.currentFloor;
System.out.printf("Current Floor: %d Direction: %s\n", N, elevator.direction);
// 检查停靠
boolean stop = false;
if (M != null && M == N) {
stop = true;
queue.removeInternal();
}
if (P != null && P.floor == N &&
(P.direction == elevator.direction || elevator.direction == Direction.IDLE)) {
stop = true;
queue.removeExternal();
}
if (stop) {
System.out.printf("Open Door # Floor %d\n", N);
System.out.println("Close Door");
}
// 方向更新逻辑
boolean hasSameDirectionRequest = false;
List<Integer> internalRequests = queue.getInternalRequests();
List<ExternalRequest> externalRequests = queue.getExternalRequests();
if (elevator.direction == Direction.UP) {
// 检查所有UP方向请求
for (Integer m : internalRequests) {
if (m > N) {
hasSameDirectionRequest = true;
break;
}
}
for (ExternalRequest p : externalRequests) {
if (p.floor > N && p.direction == Direction.UP) {
hasSameDirectionRequest = true;
break;
}
}
} else if (elevator.direction == Direction.DOWN) {
// 检查所有DOWN方向请求
for (Integer m : internalRequests) {
if (m < N) {
hasSameDirectionRequest = true;
break;
}
}
for (ExternalRequest p : externalRequests) {
if (p.floor < N && p.direction == Direction.DOWN) {
hasSameDirectionRequest = true;
break;
}
}
}
if (!hasSameDirectionRequest) {
// 切换方向检查反向请求
boolean hasReverseRequest = false;
if (elevator.direction == Direction.UP) {
for (Integer m : internalRequests) {
if (m < N) {
hasReverseRequest = true;
break;
}
}
for (ExternalRequest p : externalRequests) {
if (p.floor < N && p.direction == Direction.DOWN) {
hasReverseRequest = true;
break;
}
}
} else {
for (Integer m : internalRequests) {
if (m > N) {
hasReverseRequest = true;
break;
}
}
for (ExternalRequest p : externalRequests) {
if (p.floor > N && p.direction == Direction.UP) {
hasReverseRequest = true;
break;
}
}
}
if (hasReverseRequest) {
elevator.direction = elevator.direction == Direction.UP ? Direction.DOWN : Direction.UP;
} else {
elevator.direction = Direction.IDLE;
}
}
// 最终检查队列状态
if (queue.isEmpty()) {
elevator.direction = Direction.IDLE;
break;
}
}
}
}
第三次电梯题目的设计与分析
题目要求:
对之前电梯调度程序再次进行迭代性设计,加入乘客类(Passenger),取消乘客请求类,类设计要求遵循单一职责原则(SRP);乘客请求输入变动情况:外部请求由之前的<请求楼层数,请求方向>修改为<请求源楼层,请求目的楼层>对于外部请求,当电梯处理该请求之后(该请求出队),要将<请求源楼层,请求目的楼层>中的请求目的楼层加入到请求内部队列(加到队尾)
设计类图:
这次的题目集变化了输入楼层请求的格式,参考类图删除了ExternalRequest类,新增了一个Passager类。Main类这次用于处理输入数据的正则表达式要改变,然后这次的某一行数据按Passenger类的构造方法来储存。RequestQueue类中队列的类型变为Passenger,并且把乘客请求添加到队列里方法变为传入Passenger。Controller类移除请求的方法中要改为在处理完一个外部请求时先把外部请求中的楼层加入内部请求队列队尾部分,再移除当前这个外部请求。
Souce Monitor分析结果:
这次题目变化了输入的格式、类的重新设计和逻辑处理情况。我在其中碰到了许多的问题,比如在外部请求的删除和添加到内部末尾处有问题,还有就是逻辑上的问题,由于前一次的电梯问题我还没有发现,说明我可能有情况漏掉了,同样带到了这一次的问题当中。
三、踩坑心得
一定要有完整的思路再去开始写代码
写代码一定要有“全局观”,我刚开始想到什么就写什么,这样不仅会导致情况很可能考虑的不周到,还会影响编写其他代码的思路,最后的结果就是删删改改,可能还会越改越错。所以一定要谋而后动。
与人沟通很重要
“他山之玉,可以攻石”,有时候一个问题你思考了好久都没有解决甚至是没有头绪的时候,别人可能稍稍指点一下就会解决,团队协助,互帮互助非常重要。
一定要仔细
不要盲目的去追求速度,如果速度上去了,因为粗心大意而调试找错的时间成本可能会大于仔细认真的敲,有时候一个中英文输错&&和||写错或者大小写拼写等等问题就很难发现,但又会影响你全部的代码。
第一步很重要
这次电梯的三次实验,其实最重要的我觉得还是第一次对电梯的理解是否完全是否透彻,我就是由于不知晓某个情况没正确考虑,而导致后续的作业一直被影响。所以一定要搞明白设计需求,逻辑原理。
四、改进建议
1.代码的部分方法逻辑过于重复且繁琐,可将其拆分为多个方法,这样不仅能提高代码的可读性,也便于进行后续的维护。
2.方法尽量做到单一功能,使逻辑更清晰。
3.在一些可能越界的地方加入判断更好的检测到问题出现在哪,快速定位和解决问题。
4.增加代码的注释,提高可读性,不然自己一段时间后重新看会很费劲,降低效率。特别是复杂方法逻辑不清晰更要添加注释。
五、总结
收获
通过这三次迭代性题目集的综合性学习,我获益良多,首先,我对C语言面对过程编程和Java面对对象编程的差异有了更多的了解,再就是这三次PTA作业使我的逻辑思维和编程能力很有帮助。然后在不断编写和修改代码过程中, 我对于Java语法掌握、正则表达式应用,List使用以及面向对象的程序设计有了有更加深刻的理解和提升,通过题目的迭代设计,如电梯第一个题目迭代到第二个题目,我对于类设计的遵循单一职责原则SRP和类间关系有了更加深刻的认识。同时也理解领悟了自身还有很多的不足之处。
建议
希望老师发布新一轮的迭代作业后对前一轮的作业提供一些帮助,比如这次的三轮迭代作业,好多人可能第一次作业时没有搞懂会导致后面的都几乎没法做了,还会影响打击信心,再就是希望提交后的错误提示多一点,比如第一次的电梯作业就一个提示答案错误70分,还有就是希望迭代作业起步的时候能有更多的测试用例,后面我们对题目逻辑有一定的理解后就可以自己设置测试用例来找错误。