JAVA_OOP_单步电梯调度程序

目录

    1. 前言
    1. 单部电梯调度程序
    • 题目分析
    • 2.1 我的设计
    • 2.2 我的分析
    • 2.3 踩坑心得
    1. 单部电梯调度程序(类设计)
    • 题目分析
    • 2.1 我的设计
    • 2.2 我的分析
    • 2.3 踩坑心得
    1. 单部电梯调度程序(类设计-迭代)
    • 题目分析
    • 2.1 我的设计
    • 2.2 我的分析
    • 2.3 踩坑心得
    1. 改进建议
    1. 总结

1.前言

这是一篇做了长达三周的Java大作业,另在有两周的基础编程练习的基础上,是在老师边讲边练的基础上实现的练习。不得不说,对于我的基础来说,在短短一周的时间内实现,很难。但不代表就要放弃,因为这个大作业,也创下了很多第一次,第一次在一道题目上花这么时间,第一次写到凌晨3点(虽然还没写出来),虽然过程很痛苦,但相比于上学期明显感觉效率提升很多,对知识点的理解和应用都加深很多,虽然很累,但是很爽(?),感觉学的更踏实了,感谢同组的小伙伴们❀❀给我提供了很多帮助。

  • 所涉及知识点

1.java基础语法,输入输出,字符串,数组类的熟悉

2.类和对象的创建和使用

3.类间关联聚集关系的实现编写

4.基础的程序设计原则

5.正则表达式处理字符串

6.queue,linkedlist等内部类的使用等

  • 题量

基本上每次PTA任务都安排在3-5题,前面的题目均较简单,基本上均为大作业打基础,最后的大作业,第一次实属刁钻,也跟我们的整体水平有关,老师从一开始坚持自己钻研的态度到默默给我们发了很多解释电梯流程的文件,一周过去了总体2000次尝试通过率仍为0,还是老师放松了通过标准,我才能蒙混过关。
后面的大作业均是在第一次的基础上加上了类设计与迭代的处理,第一次主要是在电梯的算法设计上要花很多时间理顺,第二次则是MVC模式的运用,要把原来的单个电梯类拆分成许多小类,不过还好老师给了参考类图,以及类与类间方法的调用,第三次是需求和类设计的改动,通过难度比之前减少很多,可能也因为测试样例标准较松。

  • 难度

难度相对我来说还是都差不多的,逻辑思维很难,类设计绕绕的对我也很难,最后一个小点的算法没有前面那两个那么磨人但也谈不上轻松,以及要把一堆又一堆抽象的东西给模型化,思考哪个是类哪个是对象,考虑到这个对象的属性和数量,以及顺序性,以及是要对这些对象排序还是删除等,又有很多储存形式需要抉择,以及这个类跟那个类是什么关系,这个方法应该放在哪个类里,最磨人的还是电梯的算法了,这些后面再细聊吧。

2. 单部电梯调度程序

1. 设计一个电梯类
- 包含电梯的最大楼层数、最小楼层数(默认为1层)、当前楼层、运行方向、运行状态,以及电梯内部乘客的请求队列和电梯外部楼层乘客的请求队列,其中,电梯外部请求队列需要区分上行和下行。

2. 运行规则:
- 电梯请求分为内部请求和外部请求,有乘客对电梯发起请求时,电梯开始移动,当电梯向某个方向移动时,优先处理同方向的请求,当同方向的请求均被处理完毕然后再处理相反方向的请求。
- 采用串行处理乘客的请求方式。
- 当电梯停止时,如果有新的请求,就根据请求的方向或位置决定移动方向。
- 电梯在运行到某一楼层时,检查当前是否有请求(访问电梯内请求队列和电梯外请求队列),然后据此决定移动方向。
- 每次移动一个楼层,检查是否有需要停靠的请求,如果有,则开门,处理该楼层的请求,然后关门继续移动。

3. 输入格式:
- 第一行输入最小电梯楼层数。
- 第二行输入最大电梯楼层数。
- 从第三行开始每行输入代表一个乘客请求。
- 电梯内乘客请求格式:<楼层数>
- 电梯外乘客请求格式:<乘客所在楼层数,乘梯方向>,其中,乘梯方向用UP代表上行,用DOWN代表下行(UP、DOWN必须大写)。
- 当输入“end”时代表输入结束(end不区分大小写)。 

题目分析:

这一次作业的要求是实现一个LOOK算法的单电梯调度。题目的意思是第一是要服从方向优先,如果两个都符合方向优先或都不符合方向优先,那么再判断距离,如果还是一样,很显然是直接一并处理掉。

这里贴上班长写的电梯运行32种情况

这里先简要突出下核心,我对题目主要的分析在下面的踩坑心得处。

2.1 我的设计

  1. 在处理数据方面:

根据题目以及老师发的解释文档,我采用queue的形式储存,电梯请求先进先出的特性也与之相符,以及用LinkedList来对它进行实例化,因为对队列进行的主要操作是添加和删除,没有排序。若功能以排序为主即直上直下的算法的话,则用ArrayList较好。

两个队列一个用来储存内部请求, 只有楼层这一个属性。另一个用来储存外部请求,因为外部请求有楼层和方向两个属性,则用ExternalRequest这一泛型类做参数,这一泛型类内含floor和Direction两属性。

在判断完数据的合法性后,通过使用相关函数判断输入的数据的始末字符<>是否与要求相符,再用分割字符串的函数取出数据中有效的数据存入数组中,再通过判断此数据中是否含有“,”,若有就将其按逗号分割成多个子字符串,然后尝试把分割后的子字符串转换为整数和枚举类型的值,存入外部队列中。若没有就存入内部队列。

while (true) {
            String line = sc.nextLine().trim();
            if (line.equalsIgnoreCase("end")) break;

            if (line.startsWith("<") && line.endsWith(">")) {
                String s = line.substring(1, line.length() - 1).trim();
                if (s.contains(",")) {
                    String[] p = s.split(",");
                    try {
                        int f = Integer.parseInt(p[0].trim());
                        Direction d = Direction.valueOf(p[1].trim().toUpperCase());
                        e.addOuter(f, d);
                    } catch (Exception ex) {
                    }
                } else {
                    try {
                        int f = Integer.parseInt(s);
                        e.addInner(f);
                    } catch (Exception ex) {
                    }
                }
            }
        }
  1. 在电梯运行逻辑上:

针对以上8种情况,即 第一是要服从方向优先,如果两个都符合方向优先或都不符合方向优先,那么再判断距离,如果还是一样,很显然是直接一并处理掉。否则谁符合方向优先就选择谁。我不断地用if-else进行循环嵌套,虽然这种方式不可避免地会使语句分支增多,导致代码的复杂程度上升,但它能够有效地处理同向优先的逻辑,完成电梯运行的模拟。
下面是电梯确定下一目标楼层的算法

// 当内部请求队列和外部请求队列都不为空时,需要根据电梯的运行方向和请求的楼层、方向来确定目标楼层
            if (inner != null && outer != null) {
                // 如果内部请求的目标楼层在电梯当前运行方向上
                if (flag * (inner - currentFloor) > 0) {
                    // 如果外部请求的目标楼层在电梯当前运行方向上,但请求方向与电梯当前方向不同
                    if (flag * (outer.floor - currentFloor) >= 0 && direction != outer.dir) {
                        // 则将内部请求的目标楼层作为电梯的目标楼层
                        target= inner;
                    // 如果外部请求的目标楼层在电梯当前运行方向上,且请求方向与电梯当前方向相同
                    } else if (flag * (outer.floor - currentFloor) > 0 && direction == outer.dir) {
                        // 计算内部请求目标楼层与当前楼层的距离
                        int d1 = Math.abs(inner - currentFloor);
                        // 计算外部请求目标楼层与当前楼层的距离
                        int d2 = Math.abs(outer.floor - currentFloor);
                        // 选择距离当前楼层较近的目标楼层作为电梯的目标楼层
                        target = (d2 < d1) ? outer.floor : inner;
                    // 如果外部请求的目标楼层不在电梯当前运行方向上,且请求方向与电梯当前方向不同
                    } else if (flag * (outer.floor - currentFloor) <= 0 && direction != outer.dir) {
                        // 则将内部请求的目标楼层作为电梯的目标楼层
                        target = inner;
                    // 如果外部请求的目标楼层不在电梯当前运行方向上,且请求方向与电梯当前方向相同
                    } else if (flag * (outer.floor - currentFloor) <= 0 && direction == outer.dir) {
                        // 则将内部请求的目标楼层作为电梯的目标楼层
                        target = inner;
                    } else {
                        // 如果以上条件都不满足,则跳出循环
                        break;
                    }
                }

                // 如果内部请求的目标楼层不在电梯当前运行方向上
                if (flag * (inner - currentFloor) < 0) {
                    // 如果外部请求的目标楼层在电梯当前运行方向上,且请求方向与电梯当前方向相同
                    if (flag * (outer.floor - currentFloor) >= 0 && direction == outer.dir) {
                        // 则将外部请求的目标楼层作为电梯的目标楼层
                        target = outer.floor;
                    // 如果外部请求的目标楼层在电梯当前运行方向上,但请求方向与电梯当前方向不同
                    } else if (flag * (outer.floor - currentFloor) >= 0 && direction != outer.dir) {
                        // 计算内部请求目标楼层与当前楼层的距离
                        int d1 = Math.abs(inner - currentFloor);
                        // 计算外部请求目标楼层与当前楼层的距离
                        int d2 = Math.abs(outer.floor - currentFloor);
                        // 选择距离当前楼层较近的目标楼层作为电梯的目标楼层
                        target = (d2 < d1) ? outer.floor : inner;
                    // 如果外部请求的目标楼层不在电梯当前运行方向上,且请求方向与电梯当前方向相同
                    } else if (flag * (outer.floor - currentFloor) <= 0 && direction == outer.dir) {
                        // 则将内部请求的目标楼层作为电梯的目标楼层
                        target = inner;
                    // 如果外部请求的目标楼层不在电梯当前运行方向上,且请求方向与电梯当前方向不同
                    } else if (flag * (outer.floor - currentFloor) <= 0 && direction != outer.dir) {
                        // 计算内部请求目标楼层与当前楼层的距离
                        int d1 = Math.abs(inner - currentFloor);
                        // 计算外部请求目标楼层与当前楼层的距离
                        int d2 = Math.abs(outer.floor - currentFloor);
                        // 选择距离当前楼层较近的目标楼层作为电梯的目标楼层
                        target = (d2 < d1) ? outer.floor : inner;
                    } else {
                        // 如果以上条件都不满足,则跳出循环
                        break;
                    }
                }
            // 如果只有内部请求队列不为空
            } else if (inner != null) {
                // 则将内部请求的目标楼层作为电梯的目标楼层
                target = inner;
            // 如果只有外部请求队列不为空
            } else if (outer != null) {
                // 则将外部请求的目标楼层作为电梯的目标楼层
                target = outer.floor;
            } else {
                // 如果两个队列都为空,则跳出循环
                break;
            }
          }
  1. 在电梯移动与队列处理方面:
    通过方法addInner()和addOuter()入队,第一题条件较简单,只需要楼层在范围内即可入队,出队的话
    在run()方法里判断完目标楼层后,根据目标楼层与当先楼层的相对位置更新电梯的运行方向和当前楼层,并根据当前楼层输出“”,
    后判断当前楼层是否与目标楼层一致,若一致则同时删除内部和外部队列请求。
direction = target > currentFloor ? Direction.UP : Direction.DOWN;

            // 根据电梯的运行方向,更新电梯的当前楼层
            if (direction == Direction.UP) {
                currentFloor++;
            } else {
                currentFloor--;
            }

            // 输出电梯当前所在楼层和运行方向
            System.out.printf("Current Floor: %d Direction: %s\n", currentFloor, direction);

            // 标记电梯是否需要停止
            boolean stop = false;
            // 如果电梯到达了目标楼层
            if (target == currentFloor) {
                // 如果内部请求队列不为空,且队首元素就是当前楼层
                if (!innerQueue.isEmpty() && innerQueue.peek() == currentFloor) {
                    // 则将该元素从内部请求队列中移除
                    innerQueue.remove();
                }
                // 再次检查电梯是否到达了目标楼层
                if (target == currentFloor) {
                    // 如果外部请求队列不为空,且队首元素的目标楼层就是当前楼层
                    if (!outerQueue.isEmpty() && outerQueue.peek().floor == currentFloor) {
                        // 则将该元素从外部请求队列中移除
                        outerQueue.remove();
                    }
                    // 标记电梯需要停止
                    stop = true;
                }

                // 如果电梯需要停止
                if (stop) {
                    // 输出电梯开门信息
                    System.out.println("Open Door # Floor " + currentFloor);
                    // 输出电梯关门信息
                    System.out.println("Close Door");
                }
            }

2.2 我的分析

SourceMontor生成表:

参数
行数 154
语句数 116
分支语句百分比 30.2
方法调用语句数 40
含注释的行百分比 0.0
类和接口数量 4
每个类的方法数 1.50
每个方法的平均语句数 15.83
最复杂方法的行号 44
最复杂方法的名称 Elevator.run()
最大复杂度 43
最深代码块的行号 56
最大代码块深度 6
平均代码块深度 3.45
平均复杂度 10.00

3 个类中最复杂的方法:复杂度、语句数、最大深度

方法 复杂度、语句数、最大深度
Elevator.addInner() 3, 2, 3, 1
Elevator.addOuter() 3, 2, 3, 1
Elevator.Elevator() 1, 4, 2, 0
Elevator.run() 43, 61, 6, 19
ExternalRequest.ExternalRequest() 1, 2, 3, 0
Main.main() 9, 24, 6, 17

代码块深度|语句数

代码块深度 语句数
0 6
1 12
2 18
3 30
4 8
5 22
6 20

不足

  • 方法复杂度高:最复杂方法Elevator.run()复杂度高达43 ,平均复杂度也有10.00 。这意味着该方法内部逻辑复杂,大量的条件判断和嵌套使得代码可读性变差,后期修改和扩展容易引入错误。
  • 分支语句占比大:分支语句百分比为30.2% ,说明代码中存在较多的条件判断逻辑。过多的分支会使程序的执行路径变得复杂,难以梳理整体逻辑。
  • 注释匮乏:含注释的行百分比为0.0% ,这对代码的可读性和可维护性极为不利。。
  • 深度嵌套问题:最大代码块深度为6 ,平均代码块深度3.45 。较深的代码块深度意味着代码中存在较多的嵌套结构(如多层if - else、循环嵌套等) ,这会增加代码理解和调试的难度。

2.3 踩坑心得

  • 运行逻辑上

我的脑回路

说实话,一开始看题目,真的看很久都看不懂什么。

依据题目,“当有乘客对电梯发起请求时,电梯开始移动,当电梯停止时,如果有新的请求,就根据请求的方向或位置决定移动方向。”电梯运动时请求是实时的吗?但请求(输入)是一次性给出的。矛盾。

又有 “当电梯向某个方向移动时,优先处理同方向的请求,当同方向的请求均被处理完毕然后再处理相反方向的请求。”则想,这样的话,如果请求是一次性给出的情况下,那么只要电梯上至最大请求楼层后下至1楼,一个来回就可以解决所有的请求,这是我一开始觉得最快速,最符合题意的算法,他与测试样例的结果简直百分百契合,也与我在b站了解的单电梯look算法一致,一切都建立在一个基础上,请求是一次性给出且没有请求的先后顺序,只是电梯顺路就带上。

下图为pta电梯运行直上直下的尝试

but事情又又发生了转折,题中“即采用串行处理乘客的请求方式(电梯只按照规则响应请求队列中当前的乘客请求,响应结束后再响应下一个请求)”,这样的话,直上直下就被推翻了,而要考虑请求的先来后到,应该也就是请求输入时的前后顺序,这里也要考虑队列是分两个还是三个。

下面是pta上三个队列的尝试

实际上我选择性忽略了电梯的串行运行,导致前面一直浪费了很多时间在错误的逻辑上。

有点思路

事情的转机发生在老师发了电梯运行过程详解后,上面的问题都得到了准确地回答。
一,请求是一次性输入的,但是需要判断先来后到。
二,请求队列是分为内部队列和外部队列这两个队列。其中外部队列需要同时储存楼层和方向两个属性。
三,电梯不是直上直下,而是通过比较内部队列和外部队列的表头,且内部和外部队列是按前后顺序而排列。

但是问题又来了,这是个什么比较法呢?当时能判断的是,比较两队列表头,优先处理同向且距离近的楼层
但如果外部请求方向与电梯相同,但是要去外部请求楼层的方向与电梯当前运行方向不同,而内部请求与电梯当前运行方向相同,电梯该咋办?我该咋办TT,我又停滞于此了。

救赎之光

老师不久后又发了两个情况比较多的测试用例,自己虽然尝试去靠着运行结果修改,但还是思路有点乱乱的感觉,还是不自己兜圈子去问了同组的小伙伴们,终于稍稍理顺了些电梯的运行逻辑。最后写运行逻辑的时候还是照着班长画的4*8=32种电梯运行图,这里自己还是欠缺斗志,没有自己一个一个理顺的决心,实际上真正去做了并不难。

  • 代码实现上
  1. 没有区分end读入的大小写,导致一直非零返回,后用line.equalsIgnoreCase("end")解决

  2. 在判断电梯运行逻辑的时候,脑抽把if写成了while,导致一直运行超时,以为是自己写的太复杂了,还调试了很久才发现TT。

  3. 一开始没有考虑队列为空的情况,情况考虑不够周全。

2.4改进建议

  1. 要降低方法的分支数,Elevator.run 方法逻辑复杂,代码冗长,可将其中部分逻辑提取成小方法。
  2. 注释率要提高,增强代码可读性,方便自己和他人理解维护。
点击折叠代码
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

// 定义电梯运行方向的枚举类型
enum Direction {
    UP,
    DOWN,
    IDLE
}

class Elevator {
    // 电梯可到达的最小楼层
    private int minFloor;
    // 电梯可到达的最大楼层
    private int maxFloor;
    // 电梯当前所在楼层
    private int currentFloor;
    // 电梯当前运行方向
    private Direction direction;
    // 电梯内部请求队列,存储要到达的楼层
    private Queue<Integer> innerQueue = new LinkedList<>();
    // 电梯外部请求队列,存储外部请求信息
    private Queue<ExternalRequest> outerQueue = new LinkedList<>();

    // 内部静态类,用于表示电梯的外部请求
    private static class ExternalRequest {
        // 外部请求所在楼层
        int floor;
        // 外部请求的方向(向上或向下)
        Direction dir;

        // 构造函数,初始化外部请求的楼层和方向
        ExternalRequest(int floor, Direction dir) {
            this.floor = floor;
            this.dir = dir;
        }
    }

    // 电梯构造函数,初始化最小楼层、最大楼层、当前楼层和运行方向
    public Elevator(int min, int max) {
        minFloor = min;
        maxFloor = max;
        currentFloor = min;
        direction = Direction.UP;
    }

    // 向电梯内部请求队列添加请求楼层的方法
    public void addInner(int floor) {
        // 检查楼层是否在电梯可到达范围内
        if (floor >= minFloor && floor <= maxFloor) {
            innerQueue.add(floor);
        }
    }

    // 向电梯外部请求队列添加外部请求的方法
    public void addOuter(int floor, Direction dir) {
        // 检查楼层是否在电梯可到达范围内
        if (floor >= minFloor && floor <= maxFloor) {
            outerQueue.add(new ExternalRequest(floor, dir));
        }
    }

    // 电梯运行的核心方法
    public void run() {
        // 只要内部请求队列或外部请求队列不为空,就持续运行
        while (!innerQueue.isEmpty() || !outerQueue.isEmpty()) {
            // 查看内部请求队列的队首元素(但不取出)
            Integer inner = innerQueue.peek();
            // 查看外部请求队列的队首元素(但不取出)
            ExternalRequest outer = outerQueue.peek();

            int target = -1;
            int flag = 1;
            // 根据电梯当前运行方向设置标志值
            if (direction == Direction.UP) flag = 1;
            else if (direction == Direction.DOWN) flag = -1;
            // 当内部请求和外部请求都存在时
            if (inner != null && outer != null) {
                // 根据电梯方向和内部请求楼层与当前楼层的关系进行判断
                if (flag * (inner - currentFloor) > 0) {
                    // 外部请求楼层在当前楼层同方向且电梯方向与请求方向不同等情况判断
                    if (flag * (outer.floor - currentFloor) >= 0 && direction != outer.dir) {
                        target = inner;
                    } else if (flag * (outer.floor - currentFloor) > 0 && direction == outer.dir) {
                        // 计算内部请求和外部请求到当前楼层的距离
                        int d1 = Math.abs(inner - currentFloor);
                        int d2 = Math.abs(outer.floor - currentFloor);
                        // 根据距离确定目标楼层
                        target = (d2 < d1) ? outer.floor : inner;
                    } else if (flag * (outer.floor - currentFloor) <= 0 && direction != outer.dir) {
                        target = inner;
                    } else if (flag * (outer.floor - currentFloor) <= 0 && direction == outer.dir) {
                        target = inner;
                    } else break;
                }

                // 与上述情况相反的逻辑判断
                if (flag * (inner - currentFloor) < 0) {
                    if (flag * (outer.floor - currentFloor) >= 0 && direction == outer.dir) {
                        target = outer.floor;
                    } else if (flag * (outer.floor - currentFloor) >= 0 && direction != outer.dir) {
                        int d1 = Math.abs(inner - currentFloor);
                        int d2 = Math.abs(outer.floor - currentFloor);
                        target = (d2 < d1) ? outer.floor : inner;
                    } else if (flag * (outer.floor - currentFloor) <= 0 && direction == outer.dir) {
                        target = inner;
                    } else if (flag * (outer.floor - currentFloor) <= 0 && direction != outer.dir) {
                        int d1 = Math.abs(inner - currentFloor);
                        int d2 = Math.abs(outer.floor - currentFloor);
                        target = (d2 < d1) ? outer.floor : inner;
                    } else
                        break;
                }
            }
            // 只有内部请求时,目标楼层为内部请求楼层
            else if (inner != null) target = inner;
            // 只有外部请求时,目标楼层为外部请求楼层
            else if (outer != null) target = outer.floor;
            else break;

            // 根据目标楼层与当前楼层的关系确定电梯运行方向
            direction = target > currentFloor? Direction.UP : Direction.DOWN;

            // 根据运行方向更新当前楼层
            if (direction == Direction.UP) currentFloor++;
            else currentFloor--;

            // 输出电梯当前楼层和运行方向
            System.out.printf("Current Floor: %d Direction: %s\n", currentFloor, direction);

            boolean stop = false;
            // 当电梯到达目标楼层时
            if (target == currentFloor) {
                // 如果内部请求队列不为空且队首楼层是当前楼层,移除该内部请求
                if (!innerQueue.isEmpty() && innerQueue.peek() == currentFloor) {
                    innerQueue.remove();
                }
                // 如果外部请求队列不为空且队首请求楼层是当前楼层,移除该外部请求
                if (target == currentFloor) {
                    if (!outerQueue.isEmpty() && outerQueue.peek().floor == currentFloor) {
                        outerQueue.remove();
                    }
                    stop = true;
                }

                // 如果需要停止,输出开门和关门信息
                if (stop) {
                    System.out.println("Open Door # Floor " + currentFloor);
                    System.out.println("Close Door");
                }
            }
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int min = sc.nextInt();
        int max = sc.nextInt();
        sc.nextLine();

        // 创建电梯对象,传入最小楼层和最大楼层
        Elevator e = new Elevator(min, max);

        // 持续读取用户输入,直到输入"end"结束
        while (true) {
            String line = sc.nextLine().trim();
            if (line.equalsIgnoreCase("end")) break;

            // 如果输入符合电梯请求格式
            if (line.startsWith("<") && line.endsWith(">")) {
                String s = line.substring(1, line.length() - 1).trim();
                // 如果输入包含逗号,说明是外部请求
                if (s.contains(",")) {
                    String[] p = s.split(",");
                    try {
                        int f = Integer.parseInt(p[0].trim());
                        Direction d = Direction.valueOf(p[1].trim().toUpperCase());
                        // 向电梯添加外部请求
                        e.addOuter(f, d);
                    } catch (Exception ex) {
                    }
                }
                // 否则是内部请求
                else {
                    try {
                        int f = Integer.parseInt(s);
                        // 向电梯添加内部请求
                        e.addInner(f);
                    } catch (Exception ex) {
                    }
                }
            }
        }
        System.out.println("Current Floor: 1 Direction: UP");
        // 启动电梯运行
        e.run();
    }
}

3. 单步电梯调度程序(类设计)

是第一次作业的迭代

  1. 增加类设计要求遵循单一职责原则(SRP)。
  1. 增加非法数据处理:
  • 高于最高楼层数或低于最低楼层数,程序自动忽略此类输入,继续执行。
  • 输入时出现连续的相同请求,例如<3><3><3>或者<5,DOWN><5,DOWN>,处理方法:程序自动忽略相同的多余输入,继续执行,例如<3><3><3>过滤为<3>。

3.1 题目分析

对于要求1,即把原本塞到elevator这个类里的功能分到别的类中,老师也给了参考的类图,难点就是MVC设计原则的实现。

对于要求2,在第一题已经实现。
对于要求3,在ExternalRequest类中新加

 private Integer lastInnerFloor = null;
    private ExternalRequest lastOuterRequest = null;

这两个属性用于判断新加的是否与队列末的请求相同,如果不相同才添加。

3.2 我的设计

  1. 类设计方面

以下是对各代码类功能的总结性说明:

  1. Elevator:代表电梯,存储并管理电梯的基本信息,如可运行的楼层范围、当前楼层、运行方向和状态等。提供方法用于获取和设置这些属性,同时能判断楼层是否在有效范围内。
  2. ExternalRequest:表示电梯的外部请求,包含请求的楼层和方向信息。提供方法获取这些信息,并通过重写equals方法来比较请求是否相同。
  3. RequestQueue:管理电梯的请求队列,包括内部乘客的请求队列和外部乘客的请求队列。提供方法向队列中添加请求,并能避免重复添加相同的请求。
  4. Controller:作为电梯运行的控制中心,根据请求队列和电梯的当前状态,确定电梯的目标楼层并控制其移动。在电梯到达目标楼层时,处理开门、关门以及移除已处理请求等操作。
  5. Main:程序的入口,负责从控制台读取用户输入的电梯运行参数和乘客请求,创建并初始化相关对象(电梯、请求队列、控制器),然后协调这些对象模拟电梯的运行过程,直到所有请求处理完毕。

3.3 我的分析

不足

  1. 方法复杂度不均衡Controller.processRequests 方法复杂度高达27 ,是最复杂的方法,远超平均复杂度2.73 。这表明该方法内部逻辑非常复杂,可能包含大量条件判断、循环等,使得代码可读性变差,后期维护和修改容易出错。
  2. 分支语句占比与方法调用情况:分支语句百分比为19.2% ,结合方法调用语句数106 ,说明代码中条件判断和方法调用较为频繁,可能导致代码逻辑不够简洁清晰。
  3. 缺乏注释:含注释的行百分比为0.0% ,这对代码的可读性和可维护性极为不利。没有注释的情况下,其他开发者(甚至包括一段时间后的自己)很难快速理解代码意图和关键逻辑。
  4. 部分方法简单但数量多:从最复杂方法列表看,有不少方法复杂度、语句数和深度都较低(如各 gettersetter 方法 ),虽然单个方法简单,但数量较多,可能存在一定程度的代码冗余。

3.4 踩坑心得

  1. 一开始对类与类间的调用还不了解,导致在很多地方的调用都不正确。

举例未在实体类对象中传参

  1. 原先在确定目标楼层target后,直接在run()类里就直接沿用target来进行控制其移动。在电梯到达目标楼层时,处理开门、关门以及移除已处理请求等操作。在重构后原本想把target作为controller()中的属性,但尝试多次后,发现在别的类中引用controller()的属性可行性很低,在询问了大模型后明白target可以放在elevator中,通过调用target的getter和setter方法来引用和更新其值。

点击折叠代码

下面是第一次的代码,不建议打开

import java.util.LinkedList;
import java.util.Scanner;

// 定义电梯运行方向的枚举类型
enum Direction {
    UP,
    DOWN,
    IDLE
}
// 定义电梯运行状态的枚举类型
enum State {
    MOVING,
    STOPPED
}

class Elevator {
    // 电梯可到达的最小楼层
    private int minFloor;
    // 电梯可到达的最大楼层
    private int maxFloor;
    // 电梯当前所在楼层
    private int currentFloor;
    // 电梯当前运行方向
    private Direction direction;
    // 电梯当前运行状态(移动或停止)
    private State state;
    // 电梯当前要前往的目标楼层
    private Integer target;

    // 电梯构造函数,初始化电梯的基本属性
    public Elevator(int minFloor, int maxFloor) {
        this.minFloor = minFloor;
        this.maxFloor = maxFloor;
        this.currentFloor = minFloor;
        this.direction = Direction.UP;
        this.state = State.STOPPED;
    }

    // 获取电梯当前所在楼层
    public int getCurrentFloor() {
        return currentFloor;
    }

    // 获取电梯可到达的最大楼层
    public int getMaxFloor() {
        return maxFloor;
    }

    // 获取电梯可到达的最小楼层
    public int getMinFloor() {
        return minFloor;
    }

    // 获取电梯当前运行方向
    public Direction getDirection() {
        return direction;
    }

    // 获取电梯当前运行状态
    public State getState() {
        return state;
    }

    // 设置电梯当前运行状态
    public void setState(State state) {
        this.state = state;
    }

    // 设置电梯当前所在楼层
    public void setCurrentFloor(int currentFloor) {
        this.currentFloor = currentFloor;
    }

    // 设置电梯当前运行方向
    public void setDirection(Direction direction) {
        this.direction = direction;
    }

    // 判断指定楼层是否在电梯可到达范围内
    public boolean isValidFloor(int floor) {
        return floor >= minFloor && floor <= maxFloor;
    }

    // 获取电梯当前要前往的目标楼层
    public Integer getTarget() {
        return target;
    }

    // 设置电梯当前要前往的目标楼层
    public void setTarget(Integer target) {
        this.target = target;
    }
}

class ExternalRequest {
    // 外部请求所在楼层
    private Integer floor;
    // 外部请求的方向(向上或向下)
    private Direction direction;

    // 外部请求构造函数,初始化请求楼层和方向
    public ExternalRequest(Integer floor, Direction dir) {
        this.floor = floor;
        this.direction = dir;
    }

    // 获取外部请求所在楼层
    public int getFloor() {
        return floor;
    }

    // 获取外部请求的方向
    public Direction getDirection() {
        return direction;
    }

    // 重写equals方法,用于判断两个外部请求是否相等
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ExternalRequest that = (ExternalRequest) o;
        return floor.equals(that.floor) && direction == that.direction;
    }
}

class RequestQueue {
    // 电梯内部请求队列,存储要到达的楼层
    private LinkedList<Integer> innerQueue = new LinkedList<>();
    // 电梯外部请求队列,存储外部请求信息
    private LinkedList<ExternalRequest> outerQueue = new LinkedList<>();
    // 关联的电梯对象
    private Elevator elevator;
    // 记录上一个内部请求楼层,用于去重
    private Integer lastInnerFloor = null;
    // 记录上一个外部请求,用于去重
    private ExternalRequest lastOuterRequest = null;

    // 请求队列无参构造函数
    public RequestQueue() {
    }

    // 获取请求队列实例的方法
    public RequestQueue getQueueInstance() {
        RequestQueue queue = new RequestQueue();
        return queue;
    }

    // 带电梯参数的请求队列构造函数,关联电梯
    public RequestQueue(Elevator elevator) {
        this.elevator = elevator;
    }

    // 获取电梯内部请求队列
    public LinkedList<Integer> getInnerQueue() {
        return innerQueue;
    }

    // 获取电梯外部请求队列
    public LinkedList<ExternalRequest> getOuterQueue() {
        return outerQueue;
    }

    // 设置电梯外部请求队列
    public void setOuterQueue(LinkedList<ExternalRequest> outerQueue) {
        this.outerQueue = outerQueue;
    }

    // 设置电梯内部请求队列
    public void setInnerQueue(LinkedList<Integer> innerQueue) {
        this.innerQueue = innerQueue;
    }

    // 向电梯内部请求队列添加请求楼层的方法
    public void addInner(int floor) {
        // 检查楼层是否在电梯可到达范围内
        boolean isValid = elevator.isValidFloor(floor);
        // 检查是否与上一个内部请求楼层重复
        if (isValid && (lastInnerFloor == null || lastInnerFloor != floor)) {
            innerQueue.add(floor);
            lastInnerFloor = floor;
            lastOuterRequest = null;
        }
    }

    // 向电梯外部请求队列添加外部请求的方法
    public void addOuter(ExternalRequest request) {
        int floor = request.getFloor();
        // 检查楼层是否在电梯可到达范围内
        if (!elevator.isValidFloor(floor)) {
            return;
        }
        // 检查是否与上一个外部请求重复
        if (lastOuterRequest == null || !lastOuterRequest.equals(request)) {
            outerQueue.add(request);
            lastOuterRequest = request;
            lastInnerFloor = null;
        }
    }
}

class Controller {
    // 关联的电梯对象
    private Elevator elevator;
    // 关联的请求队列对象
    private RequestQueue queue;

    // 带电梯和请求队列参数的构造函数,初始化控制器
    public Controller(Elevator elevator, RequestQueue queue) {
        this.elevator = elevator;
        this.queue = queue;
    }

    // 控制器无参构造函数
    public Controller() {
    }

    // 设置关联的电梯对象
    public void setElevator(Elevator elevator) {
        this.elevator = elevator;
    }

    // 获取关联的电梯对象
    public Elevator getElevator() {
        return elevator;
    }

    // 设置关联的请求队列对象
    public void setQueue(RequestQueue queue) {
        this.queue = queue;
    }

    // 获取关联的请求队列对象
    public RequestQueue getQueue() {
        return queue;
    }

    // 处理电梯请求的核心方法
    public void processRequests() {
        // 获取电梯内部请求队列
        LinkedList<Integer> innerQueue = queue.getInnerQueue();
        // 获取电梯外部请求队列
        LinkedList<ExternalRequest> outerQueue = queue.getOuterQueue();
        // 获取电梯当前运行方向
        Direction direction = elevator.getDirection();
        // 获取电梯当前所在楼层
        int currentFloor = elevator.getCurrentFloor();

        // 只要内部或外部请求队列不为空,就持续处理请求
        while (!innerQueue.isEmpty() || !outerQueue.isEmpty()) {
            // 查看内部请求队列的队首元素(但不取出)
            Integer inner = innerQueue.peek();
            // 查看外部请求队列的队首元素(但不取出)
            ExternalRequest outer = outerQueue.peek();

            int target = -1;
            // 根据电梯当前运行方向设置标志值
            int flag = (direction == Direction.UP) ? 1 : -1;

            // 当内部请求和外部请求都存在时
            if (inner != null && outer != null) {
                // 计算内部请求楼层与当前楼层的距离
                int innerDist = inner - currentFloor;
                // 计算外部请求楼层与当前楼层的距离
                int outerDist = outer.getFloor() - currentFloor;

                // 根据电梯方向和内部请求楼层与当前楼层的关系进行判断
                if (flag * innerDist > 0) {
                    // 外部请求楼层在当前楼层同方向且电梯方向与请求方向不同等情况判断
                    if (flag * outerDist >= 0 && direction != outer.getDirection()) {
                        target = inner;
                    } else if (flag * outerDist > 0 && direction == outer.getDirection()) {
                        // 根据距离确定目标楼层
                        target = (Math.abs(outerDist) < Math.abs(innerDist)) ? outer.getFloor() : inner;
                    } else {
                        target = inner;
                    }
                } else {
                    // 与上述情况相反的逻辑判断
                    if (flag * outerDist >= 0) {
                        target = (direction == outer.getDirection()) ? outer.getFloor() : inner;
                    } else {
                        int d1 = Math.abs(innerDist);
                        int d2 = Math.abs(outerDist);
                        target = (d2 < d1) ? outer.getFloor() : inner;
                    }
                }
            }
            // 只有内部请求时,目标楼层为内部请求楼层
            else if (inner != null) {
                target = inner;
            }
            // 只有外部请求时,目标楼层为外部请求楼层
            else if (outer != null) {
                target = outer.getFloor();
            }
            // 没有请求时,跳出循环
            else {
                break;
            }

            // 设置电梯的目标楼层
            elevator.setTarget(target);
            // 根据目标楼层与当前楼层的关系确定电梯运行方向
            Direction newDirection = (target > currentFloor) ? Direction.UP : Direction.DOWN;
            // 设置电梯的运行方向
            elevator.setDirection(newDirection);

            // 根据运行方向更新当前楼层
            int nextFloor = currentFloor + (newDirection == Direction.UP ? 1 : -1);
            // 设置电梯的当前楼层
            elevator.setCurrentFloor(nextFloor);
            // 输出电梯当前楼层和运行方向
            System.out.printf("Current Floor: %d Direction: %s\n", nextFloor, newDirection);

            // 当电梯到达目标楼层时
            if (nextFloor == target) {
                // 执行开门操作
                openDoors();
                // 如果内部请求队列不为空且队首楼层是当前楼层,移除该内部请求
                if (!innerQueue.isEmpty() && innerQueue.peek() == target) {
                    innerQueue.poll();
                }
                // 如果外部请求队列不为空且队首请求楼层是当前楼层,移除该外部请求
                if (!outerQueue.isEmpty() && outerQueue.peek().getFloor() == target) {
                    outerQueue.poll();
                }
            }

            // 更新当前楼层和运行方向变量
            currentFloor = elevator.getCurrentFloor();
            direction = elevator.getDirection();
        }

        // 当内部和外部请求队列都为空时
        if (innerQueue.isEmpty() && outerQueue.isEmpty()) {
            // 设置电梯方向为空闲
            elevator.setDirection(Direction.IDLE);
            // 清空电梯目标楼层
            elevator.setTarget(null);
        }
    }

    // 判断电梯是否应在指定楼层停止的方法
    public boolean shouldStop(int floor) {
        return elevator.getCurrentFloor() == floor;
    }

    // 获取电梯下一个楼层的方法
    public Integer getNextFloor() {
        // 根据电梯当前方向计算下一个楼层并更新
        if (elevator.getDirection() == Direction.UP) {
            int newFloor = elevator.getCurrentFloor() + 1;
            elevator.setCurrentFloor(newFloor);
        } else if (elevator.getDirection() == Direction.DOWN) {
            int newFloor = elevator.getCurrentFloor() - 1;
            elevator.setCurrentFloor(newFloor);
        }
        return elevator.getCurrentFloor();
    }

    // 执行电梯开门操作的方法
    public void openDoors() {
        // 判断是否应在目标楼层开门
        if (shouldStop(elevator.getTarget())) {
            int currentFloor = elevator.getCurrentFloor();
            // 输出开门和关门信息
            System.out.println("Open Door # Floor " + currentFloor);
            System.out.println("Close Door");
            // 移除当前楼层的请求
            removeRequests();
            // 清空电梯目标楼层
            elevator.setTarget(null);
        }
    }

    // 移除当前楼层请求的方法
    public void removeRequests() {
        // 当电梯目标楼层与当前楼层相同时
        if (elevator.getTarget() == elevator.getCurrentFloor()) {
            // 如果内部请求队列不为空且队首楼层是当前楼层,移除该内部请求
            if (!queue.getInnerQueue().isEmpty() && queue.getInnerQueue().peek() == elevator.getCurrentFloor()) {
                queue.getInnerQueue().poll();
            }
            // 如果外部请求队列不为空且队首请求楼层是当前楼层,移除该外部请求
            if (!queue.getOuterQueue().isEmpty() && queue.getOuterQueue().peek().getFloor() == elevator.getCurrentFloor()) {
                queue.getOuterQueue().poll();
            }
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int min = scanner.nextInt();
        int max = scanner.nextInt();
        scanner.nextLine();

        // 创建电梯对象,传入最小楼层和最大楼层
        Elevator elevator = new Elevator(min, max);
        // 创建请求队列对象,关联电梯
        RequestQueue queue = new RequestQueue(elevator);
        // 创建控制器对象,关联电梯和请求队列
        Controller controller = new Controller(elevator, queue);

        // 持续读取用户输入,直到输入"END"结束
        while (scanner.hasNextLine()) {
            String line = scanner.nextLine().trim();
            if (line.equalsIgnoreCase("END")) {
                break;
            }
            if (line.isEmpty()) {
                continue;
            }
            try {
                // 判断输入是否符合电梯请求格式
                if (line.matches("<\\d+\\s*(,\\s*(UP|DOWN)\\s*)?>")) {
                    String cleanLine = line.replaceAll("[<>\\s]", "");
                    String[] parts = cleanLine.split(",");
                    // 如果输入只有一个参数,是内部请求
                    if (parts.length == 1) {
                        int floor = Integer.parseInt(parts[0]);
                        // 检查楼层是否在电梯可到达范围内,是则添加内部请求
                        if (elevator.isValidFloor(floor)) {
                            queue.addInner(floor);
                        }
                    }
                    // 如果输入有两个参数,是外部请求
                    else if (parts.length == 2) {
                        int floor = Integer.parseInt(parts[0]);
                        Direction dir = Direction.valueOf(parts[1].toUpperCase());
                        // 检查楼层是否在电梯可到达范围内,是则添加外部请求
                        if (elevator.isValidFloor(floor)) {
                            ExternalRequest request = new ExternalRequest(floor, dir);
                            queue.addOuter(request);
                        }
                    }
                }
            } catch (Exception e) {
            }
        }
        scanner.close();

        System.out.println("Current Floor: 1 Direction: UP");
        // 持续运行电梯,直到内部、外部请求队列为空且无目标楼层
        while (true) {
            boolean innerEmpty = queue.getInnerQueue().isEmpty();
            boolean outerEmpty = queue.getOuterQueue().isEmpty();
            Integer target = elevator.getTarget();
            if (innerEmpty && outerEmpty && target == null) {
                break;
            }
            // 如果没有目标楼层,处理请求
            if (target == null) {
                controller.processRequests();
            }
            target = elevator.getTarget();
            // 如果有目标楼层且当前楼层不等于目标楼层,移动电梯并输出信息
            if (target != null) {
                int currentFloor = elevator.getCurrentFloor();
                Direction currentDir = elevator.getDirection();
                if (currentFloor != target) {
                    int nextFloor;
                    if (currentDir == Direction.UP) {
                        nextFloor = currentFloor + 1;
                    } else {
                        nextFloor = currentFloor - 1;
                    }
                    elevator.setCurrentFloor(nextFloor);
                    System.out.printf("Current Floor: %d Direction: %s\n", nextFloor, currentDir);
                }
                // 如果当前楼层等于目标楼层,执行开门操作
                if (elevator.getCurrentFloor() == target) {
                    controller.openDoors();
                }
            }
        }
    }
}

4. 单步电梯调度程序(类设计-迭代)

第二次作业的的迭代

  1. 乘客请求输入变动情况:
  • 外部请求由之前的<请求楼层数,请求方向>修改为<请求源楼层,请求目的楼层>
  1. 对于外部请求,当电梯处理该请求之后(该请求出队),要将<请求源楼层,请求目的楼层>中的请求目的楼层加入到请求内部队列(加到队尾)
  2. 输入格式:
    电梯外乘客请求格式:<请求源楼层,请求目的楼层>,其中,请求源楼层表示乘客发起请求所在的楼层,请求目的楼层表示乘客想要到达的

** 题目分析**

  1. 对于要求一,首先之前的正则表达式形式要改
  2. 对于要求二,使用 externalMatcher.group()获取正则表达式中捕获的内容,并将其转换为整数,分别赋值给 source 和 dest。同理,内部请求也如此添加。
  • 创建一个迭代器 iterator 来遍历外部请求队列 externals。在每次循环中,获取队列中的下一个 Passenger 对象,并存储在 p 变量中。
  • 检查当前的 Passenger 对象 p 是否满足以下条件:
    p.getSourceFloor() != null:确保请求的源楼层不为空。
    p.getSourceFloor() == currentFloor:请求的源楼层等于电梯当前所在楼层。
    p.getDirection() == direction:请求的方向与电梯当前运行方向一致。
    p == currentExternalRequest:当前请求是正在处理的外部请求(currentExternalRequest 变量用于跟踪当前正在处理的外部请求)。
  • 如果上述条件都满足,说明电梯已经到达该外部请求的源楼层并且正在处理该请求。此时,创建一个新的 Passenger 对象,其源楼层为 null(因为这是内部请求,不需要源楼层信息),目的楼层为当前请求的目的楼层 p.getDestinationFloor(),并将其添加到内部请求队列中。

3.1 我的设计

3.2 我的分析

不足

  1. 方法复杂度不均衡Controller.getNextFloor 方法复杂度高达44 ,远远超过平均复杂度3.11 ,是最复杂的方法。这表明该方法内包含大量的条件判断、循环等结构,使得代码可读性变差,后期维护和修改时容易出错。
  2. 分支语句占比与方法调用情况:分支语句百分比为25.0% ,方法调用语句数为136 ,说明代码中条件判断和方法调用较为频繁,这可能导致代码逻辑不够简洁清晰,增加了理解代码执行流程的难度。
  3. 缺乏注释:含注释的行百分比为0.0% ,这对代码的可读性和可维护性极为不利。没有注释的情况下,其他开发者(甚至包括一段时间后的自己)很难快速理解代码意图和关键逻辑。
点击折叠代码

下面是第一次的代码,不建议打开

package Elevator3;
import java.util.LinkedList;
import java.util.Scanner;

// 定义电梯运行方向的枚举类型
enum Direction {
    // 向上运行
    UP,
    // 向下运行
    DOWN,
    // 静止
    IDLE
}

// 定义电梯状态的枚举类型
enum State {
    // 正在移动
    MOVING,
    // 停止
    STOPPED
}

// 电梯类,用于表示电梯的基本属性和行为
class Elevator {
    // 电梯可到达的最低楼层
    private int minFloor;
    // 电梯可到达的最高楼层
    private int maxFloor;
    // 电梯当前所在楼层
    private int currentFloor;
    // 电梯当前运行方向
    private Direction direction;
    // 电梯当前状态
    private State state;
    // 电梯的目标楼层
    private Integer target;

    // 构造函数,初始化电梯的最低楼层、最高楼层、当前楼层、运行方向和状态
    public Elevator(int minFloor, int maxFloor) {
        this.minFloor = minFloor;
        this.maxFloor = maxFloor;
        this.currentFloor = minFloor;
        this.direction = Direction.UP;
        this.state = State.STOPPED;
    }

    // 获取电梯当前所在楼层
    public int getCurrentFloor() {
        return currentFloor;
    }

    // 获取电梯可到达的最高楼层
    public int getMaxFloor() {
        return maxFloor;
    }

    // 获取电梯可到达的最低楼层
    public int getMinFloor() {
        return minFloor;
    }

    // 获取电梯当前运行方向
    public Direction getDirection() {
        return direction;
    }

    // 获取电梯当前状态
    public State getState() {
        return state;
    }

    // 设置电梯当前状态
    public void setState(State state) {
        this.state = state;
    }

    // 设置电梯当前所在楼层
    public void setCurrentFloor(int currentFloor) {
        this.currentFloor = currentFloor;
    }

    // 设置电梯当前运行方向
    public void setDirection(Direction direction) {
        this.direction = direction;
    }

    // 检查指定楼层是否在电梯可到达的范围内
    public boolean isValidFloor(int floor) {
        return floor >= minFloor && floor <= maxFloor;
    }

    // 获取电梯的目标楼层
    public Integer getTarget() {
        return target;
    }

    // 设置电梯的目标楼层,并根据目标楼层更新电梯运行方向
    public void setTarget(Integer target) {
        this.target = target;
        if (target != null) {
            this.direction = target > currentFloor? Direction.UP : Direction.DOWN;
        } else {
            this.direction = Direction.IDLE;
        }
    }

    // 增加一个方法用于打印电梯的基本信息
    public void printElevatorInfo() {
        System.out.println("Elevator Info:");
        System.out.println("Current Floor: " + currentFloor);
        System.out.println("Direction: " + direction);
        System.out.println("State: " + state);
        System.out.println("Min Floor: " + minFloor);
        System.out.println("Max Floor: " + maxFloor);
    }
}

// 乘客类,用于表示乘客的请求信息
class passenger {
    // 乘客所在的起始楼层
    private Integer sourceFloor = null;
    // 乘客要到达的目标楼层
    private Integer destinationFloor = null;

    // 无参构造函数
    public passenger() {
    }

    // 带起始楼层和目标楼层的构造函数
    public passenger(Integer sourceFloor, Integer destinationFloor) {
        this.sourceFloor = sourceFloor;
        this.destinationFloor = destinationFloor;
    }

    // 只带目标楼层的构造函数
    public passenger(Integer destinationFloor) {
        this.destinationFloor = destinationFloor;
    }

    // 获取乘客所在的起始楼层
    public Integer getSourceFloor() {
        return sourceFloor;
    }

    // 设置乘客所在的起始楼层
    public void setSourceFloor(Integer sourceFloor) {
        this.sourceFloor = sourceFloor;
    }

    // 获取乘客要到达的目标楼层
    public Integer getDestinationFloor() {
        return destinationFloor;
    }

    // 设置乘客要到达的目标楼层
    public void setDestinationFloor(Integer destinationFloor) {
        this.destinationFloor = destinationFloor;
    }

    // 根据起始楼层和目标楼层确定乘客的移动方向
    public Direction getDirection() {
        return destinationFloor > sourceFloor? Direction.UP : Direction.DOWN;
    }

    // 增加方法用于打印乘客请求信息
    public void printPassengerRequest() {
        System.out.println("Passenger Request:");
        System.out.println("Source Floor: " + sourceFloor);
        System.out.println("Destination Floor: " + destinationFloor);
    }
}

// 请求队列类,用于管理电梯的内部请求和外部请求
class RequestQueue {
    // 内部请求队列,存储电梯内部乘客按下的楼层
    private LinkedList<Integer> innerQueue = new LinkedList<>();
    // 外部请求队列,存储电梯外部乘客的请求
    private LinkedList<passenger> outerQueue = new LinkedList<>();
    // 关联的电梯对象
    private Elevator elevator;
    // 记录内部队列的最后一个楼层
    private Integer lastInnerFloor = null;
    // 记录外部队列的最后一个请求
    private passenger lastOuterRequest = null;

    // 无参构造函数
    public RequestQueue() {
    }

    // 获取请求队列实例
    public RequestQueue getQueueInstance() {
        RequestQueue queue = new RequestQueue();
        return queue;
    }

    // 带电梯对象的构造函数
    public RequestQueue(Elevator elevator) {
        this.elevator = elevator;
    }

    // 获取内部请求队列
    public LinkedList<Integer> getInnerQueue() {
        return innerQueue;
    }

    // 获取外部请求队列
    public LinkedList<passenger> getOuterQueue() {
        return outerQueue;
    }

    // 设置外部请求队列
    public void setOuterQueue(LinkedList<passenger> outerQueue) {
        this.outerQueue = outerQueue;
    }

    // 设置内部请求队列
    public void setInnerQueue(LinkedList<Integer> innerQueue) {
        this.innerQueue = innerQueue;
    }

    // 向内部请求队列添加请求
    public void addInner(int floor) {
        // 检查楼层是否有效,并且避免重复添加相同的楼层
        boolean isValid = elevator.isValidFloor(floor);
        if (isValid && (lastInnerFloor == null || lastInnerFloor != floor)) {
            innerQueue.add(floor);
            lastInnerFloor = floor;
            lastOuterRequest = null;
        }
    }

    // 向外部请求队列添加请求
    public void addOuter(passenger request) {
        int floor = request.getSourceFloor();
        // 检查楼层是否有效
        if (!elevator.isValidFloor(floor)) {
            return;
        }
        // 避免重复添加相同的请求
        if (lastOuterRequest == null || !lastOuterRequest.equals(request)) {
            outerQueue.add(request);
            lastOuterRequest = request;
            lastInnerFloor = null;
        }
    }

    // 增加方法用于打印内部请求队列信息
    public void printInnerQueueInfo() {
        System.out.println("Inner Request Queue:");
        for (Integer floor : innerQueue) {
            System.out.print(floor + " ");
        }
        System.out.println();
    }

    // 增加方法用于打印外部请求队列信息
    public void printOuterQueueInfo() {
        System.out.println("Outer Request Queue:");
        for (passenger p : outerQueue) {
            p.printPassengerRequest();
        }
    }
}

// 电梯控制器类,用于处理电梯的请求和运行逻辑
class Controller {
    // 关联的电梯对象
    private Elevator elevator;
    // 关联的请求队列对象
    private RequestQueue queue;

    // 带电梯对象和请求队列对象的构造函数
    public Controller(Elevator elevator, RequestQueue queue) {
        this.elevator = elevator;
        this.queue = queue;
    }

    // 无参构造函数
    public Controller() {
    }

    // 设置关联的电梯对象
    public void setElevator(Elevator elevator) {
        this.elevator = elevator;
    }

    // 获取关联的电梯对象
    public Elevator getElevator() {
        return elevator;
    }

    // 设置关联的请求队列对象
    public void setQueue(RequestQueue queue) {
        this.queue = queue;
    }

    // 获取关联的请求队列对象
    public RequestQueue getQueue() {
        return queue;
    }

    // 处理电梯的请求
    public void processRequests() {
        // 获取内部请求队列和外部请求队列
        LinkedList<Integer> innerQueue = queue.getInnerQueue();
        LinkedList<passenger> outerQueue = queue.getOuterQueue();
        // 获取电梯当前运行方向和所在楼层
        Direction direction = elevator.getDirection();
        int currentFloor = elevator.getCurrentFloor();

        // 当内部请求队列或外部请求队列不为空时,继续处理请求
        while (!innerQueue.isEmpty() || !outerQueue.isEmpty()) {
            // 获取内部请求队列和外部请求队列的队首元素
            Integer inner = innerQueue.peek();
            passenger outer = outerQueue.peek();

            // 初始化目标楼层为 -1
            int target = -1;
            // 根据电梯运行方向设置标志位
            int flag = (direction == Direction.UP) ? 1 : -1;

            // 根据内部请求和外部请求的情况确定目标楼层
            if (inner != null && outer != null) {
                int innerDist = inner - currentFloor;
                int outerDist = outer.getSourceFloor() - currentFloor;

                if (flag * innerDist > 0) {
                    if (flag * outerDist >= 0 && direction != outer.getDirection()) {
                        target = inner;
                    } else if (flag * outerDist > 0 && direction == outer.getDirection()) {
                        target = (Math.abs(outerDist) < Math.abs(innerDist)) ? outer.getSourceFloor() : inner;
                    } else {
                        target = inner;
                    }
                } else {
                    if (flag * outerDist >= 0) {
                        target = (direction == outer.getDirection()) ? outer.getSourceFloor() : inner;
                    } else {
                        int d1 = Math.abs(innerDist);
                        int d2 = Math.abs(outerDist);
                        target = (d2 < d1) ? outer.getSourceFloor() : inner;
                    }
                }
            } else if (inner != null) {
                target = inner;
            } else if (outer != null) {
                target = outer.getSourceFloor();
            } else {
                break;
            }

            // 设置电梯的目标楼层和运行方向
            elevator.setTarget(target);
            Direction newDirection = (target > currentFloor) ? Direction.UP : Direction.DOWN;
            elevator.setDirection(newDirection);

            // 计算电梯的下一个楼层
            int nextFloor = currentFloor + (newDirection == Direction.UP ? 1 : -1);
            elevator.setCurrentFloor(nextFloor);
            // 输出电梯的当前楼层和运行方向
            System.out.printf("Current Floor: %d Direction: %s\n", nextFloor, newDirection);

            // 当电梯到达目标楼层时,开门并处理请求
            if (nextFloor == target) {
                openDoors();
                if (!innerQueue.isEmpty() && innerQueue.peek() == target) {
                    innerQueue.poll();
                }
                if (!outerQueue.isEmpty() && outerQueue.peek().getSourceFloor() == target) {
                    passenger processedpassenger = outerQueue.poll();
                    int destination = processedpassenger.getDestinationFloor();
                    queue.addInner(destination);
                }
            }

            // 更新电梯的当前楼层和运行方向
            currentFloor = elevator.getCurrentFloor();
            direction = elevator.getDirection();
        }

        // 当内部请求队列和外部请求队列都为空时,将电梯设置为空闲状态
        if (innerQueue.isEmpty() && outerQueue.isEmpty()) {
            elevator.setDirection(Direction.IDLE);
            elevator.setTarget(null);
        }
    }

    // 判断电梯是否应该在指定楼层停止
    public boolean shouldStop(int floor) {
        return elevator.getCurrentFloor() == floor;
    }

    // 获取电梯的下一个楼层
    public Integer getNextFloor() {
        if (elevator.getDirection() == Direction.UP) {
            int newFloor = elevator.getCurrentFloor() + 1;
            elevator.setCurrentFloor(newFloor);
        } else if (elevator.getDirection() == Direction.DOWN) {
            int newFloor = elevator.getCurrentFloor() - 1;
            elevator.setCurrentFloor(newFloor);
        }
        return elevator.getCurrentFloor();
    }

    // 打开电梯门
    public void openDoors() {
        if (shouldStop(elevator.getTarget())) {
            int currentFloor = elevator.getCurrentFloor();
            System.out.println("Open Door # Floor " + currentFloor);
            System.out.println("Close Door");
            elevator.setTarget(null);
        }
    }

    // 增加方法用于打印控制器相关信息
    public void printControllerInfo() {
        System.out.println("Controller Info:");
        elevator.printElevatorInfo();
        queue.printInnerQueueInfo();
        queue.printOuterQueueInfo();
    }
}

// 主类,程序的入口点
public class Main {
    public static void main(String[] args) {
        // 创建 Scanner 对象用于读取用户输入
        Scanner scanner = new Scanner(System.in);
        // 读取电梯的最低楼层和最高楼层
        int min = scanner.nextInt();
        int max = scanner.nextInt();
        scanner.nextLine();

        // 创建电梯对象、请求队列对象和控制器对象
        Elevator elevator = new Elevator(min, max);
        RequestQueue queue = new RequestQueue(elevator);
        Controller controller = new Controller(elevator, queue);

        // 读取用户输入的请求,直到输入 "END" 为止
        while (scanner.hasNextLine()) {
            String line = scanner.nextLine().trim();
            if (line.equalsIgnoreCase("END")) {
                break;
            }
            if (line.isEmpty()) {
                continue;
            }
            try {
                // 处理内部请求和外部请求
                if (line.matches("<\\d+\\s*(,\\s*\\d+\\s*)?>")) {
                    String cleanLine = line.replaceAll("[<>\\s]", "");
                    String[] parts = cleanLine.split(",");
                    if (parts.length == 1) {
                        int floor = Integer.parseInt(parts[0]);
                        if (elevator.isValidFloor(floor)) {
                            queue.addInner(floor);
                        }
                    } else if (parts.length == 2) {
                        int source = Integer.parseInt(parts[0]);
                        int destination = Integer.parseInt(parts[1]);
                        if (elevator.isValidFloor(source) && elevator.isValidFloor(destination)) {
                            passenger request = new passenger(source, destination);
                            queue.addOuter(request);
                        }
                    }
                }
            } catch (Exception e) {
            }
        }
        // 关闭 Scanner 对象
        scanner.close();

        // 输出电梯的初始状态
        System.out.println("Current Floor: 1 Direction: UP");
        // 循环处理电梯的请求,直到所有请求处理完毕
        while (true) {
            boolean innerEmpty = queue.getInnerQueue().isEmpty();
            boolean outerEmpty = queue.getOuterQueue().isEmpty();
            Integer target = elevator.getTarget();
            if (innerEmpty && outerEmpty && target == null) {
                break;
            }
            if (target == null) {
                controller.processRequests();
            }
            target = elevator.getTarget();
            if (target != null) {
                int currentFloor = elevator.getCurrentFloor();
                Direction currentDir = elevator.getDirection();
                if (currentFloor != target) {
                    int nextFloor;
                    if (currentDir == Direction.UP) {
                        nextFloor = currentFloor + 1;
                    } else {
                        nextFloor = currentFloor - 1;
                    }
                    elevator.setCurrentFloor(nextFloor);
                    System.out.printf("Current Floor: %d Direction: %s\n", nextFloor, currentDir);
                }
                if (elevator.getCurrentFloor() == target) {
                    controller.openDoors();
                }
            }
        }

        // 打印最终的控制器信息
        controller.printControllerInfo();
    }
}

5. 改进建议

1.提高代码注释率。提交的时候都没怎么写注释,现在写blog倒回去看的时候都有点晕晕的了,以后要在重要的地方写上注释,把注释写好看,提高代码的可读性。
2.提高类的单一职责原则的实现。虽然已经分了几个类,但类里的职责还是全都堆在一个方法里。
3.减少分支语句占比和方法调用。我的运行逻辑代码中充斥着大量的if-else语句,使运行效率低,后面要学习怎么把这一大串的暴力改好。
5.要提高逻辑思维能力,不怕难题敢啃题。这次有些比较关键的算法都是学习了别人的,虽然读懂了但还是希望以后自己也能琢磨出来。
6.要读懂需求!!!不能自己乱理解就写代码,要先把需求读懂,做好类设计,再下手写代码会事半功倍!!
7.还是有很多很好用的东西(比如迭代器以及好用的函数)不会用,后面要多深入学习这些能提高效率的算法。

6. 总结

  1. 需求!!!不多说,一定要看懂题目需求再开敲啊呜呜
  2. 越复杂的题目越要把它抽象化,厘清有几个类,类里有哪些属性,有哪些方法,类与类之间的关系,同时根据处理数据的功能决定要如何存储数据。
  3. 同时要注意几大设计原则的实现,降低耦合性。
  4. 畏难情绪可以有,但是有些东西没有你想象中那么难,再坚持坚持吧,你和别人的差距没有那么大。
  5. IDEA补全弄些模板就好了,其余的都要自己来。
posted @ 2025-04-20 23:30  YXYA  阅读(109)  评论(0)    收藏  举报