题目集5——7总结Blog

前言:

这3次题目集主要在正则表达式的应用,如何去设计不同的类以及不同类之间的使用,难度渐进式提升,知识点也不断扩展。题目集5主要是对单个类的设计,知识点有身份证校验码的求值,解一元二次方程等。而从题目集6则开始设计多个类,如在汽车风挡雨刷问题中则要求我们设计4个类,并给出类之间的关系。题目集7中则学会了如何用蒙特卡洛方法求圆周率,算是知识扩展了。题目集的小作业都比较简单,基本都给出了类的设计图,只需要按照设计图走就基本没有问题了。而这次题目集最难也是最关键的便是大作业-“电梯调度”问题了。该题不同于现实的电梯需要考虑时间问题从而找出最优路径,该题的算法逻辑很像“Look”算法,即在当前方向上服务所有请求直到没有更多请求为止,然后改变方向。并且该题只考虑头部请求,因此大大降低了题目难度,但仍然有不小的挑战。而往后的大作业便是以此迭代,难度也依次递增。

题目集5单部电梯调度程序

目标:通过创建电梯类来模拟电梯的运行并且优先处理同方向的请求,以及在移动过程中处理顺路的请求

设计模式:生产者—消费者模式

算法:Look算法

设计与分析:

类设计:

Elavator电梯类,进行电梯的上升下降和开关门行为。

private int minFloor;//最低楼层
private int maxFloor;//最高楼层
private int currentFloor = 1;//初始楼层
private String direction = "UP";//初始方向
private int[] internalRequests;//内部请求队列
private int internalRequestCount = 0;//初始化内部请求数量
private int[][] outRequests;//外部请求队列
private int outRequestCount = 0;//初始化外部请求数量
private boolean change1 = false;//标记1
private boolean change2 = false;//标记2
public void getInterRequest(int floor)//添加内部请求
public void getInterAndOutside(int floor, String direction)//添加外部请求
public void removeInterRequest()//删除内部请求
public void removeOutsideRequest()//删除外部请求
public void process()//运行
public void move()//移动
public void handleCurrentFloor()//处理当前楼层是否开门
public void ifDirectionChange()//是否要换方向

类图:

 

 

报表内容:

报表内容分析:

代码共 242 行,含 98 条语句,注释行占比 21.1,算是比较可以的了(对我而言,有1/5的注释已经不错了)平均每个类有 2 个方法,每个方法平均含 20.75 条语句。最大复杂度为 5,平均复杂度为3.00,从图中可以看出我的代码最大复杂度和平均复杂度都远大于给定的界限,说明我的代码还是不够简洁,复杂度太高了方法逻辑较复杂,不利于理解、调试与复用。并且支语句占比 32.7%,过多的分支会使代码执行流程不够清晰,增加出错风险与理解成本。尽管平均块深度 1.18,但最大块深度为 4,存在一定嵌套层次,可能导致局部逻辑难以快速把握。不过我的方法数量和平均方法数量还是在绿线以内的说明方法数量合理不多也不少。

踩坑心得:

这次作业可谓是难倒一众学生(?),在规定的时间之内无一人做出来。其实原因很简单,对题目的理解不够深刻,或者说对电梯运行的理解不够(当然这个电梯也是超出现实的电梯运行)。本次电梯运行是不管运行时间而遵守同方向请求优先处理,而一开始的方向又一定是上升的,故而在一开始时并不需要判断是否上升或者下降(经过大量测试得出),并且第一个请求也不会是初始楼层故而不需要对其进行特殊处理。在排除这些后就可以继续编写代码。而在最初编写中最频繁发生的是运行超时(逻辑陷入死循环)。

而在不断查找中我发现了一个地方的逻辑有些不对

 private void ifDirectionChange() {
        if ("UP".equals(direction)) {
            boolean hasUpRequest = false;
            // 检查内部请求
                if (internalRequests[0] > currentFloor) {
                    hasUpRequest = true;
                }
            if (!hasUpRequest) {
                    if (outRequests[0][0] > currentFloor && outRequests[0][1] == 1) {
                        hasUpRequest = true;
                    }
                }
            if(!hasUpRequest) {
                if(outRequests[0][0] > currentFloor && outRequests[0][1] == 2) {
                    hasUpRequest = true;<------此处标记
                }
                }
            if (!hasUpRequest) {
                direction = "DOWN";
            }
        } 
          else {
            boolean hasDownRequest = false;
                if (internalRequests[0] < currentFloor) {
                    hasDownRequest = true;
                }
            if (!hasDownRequest) {
                if(outRequests[0][0] < minFloor) {
                    hasDownRequest = false;
                    }
                else {
                    if (outRequests[0][0] < currentFloor && outRequests[0][1] == 2) {
                        hasDownRequest = true; 
                    if(outRequests[0][0] > currentFloor && outRequests[0][1] == 2) {<----------此处标记
                    }
                }
            if(!hasDownRequest) {
                if(outRequests[0][0] < currentFloor && outRequests[0][1] == 1) {
                    hasDownRequest = true;<---------此处标记
                }
                }
                }
            }
            if (!hasDownRequest) {
                direction = "UP";
            }
        }
    }

在标记处很明显多出一段意义不明的代码,应该删除。当然这部分的逻辑也有待修改,如在上升过程中如果没有上升请求但高处有下降请求那么应该上升至该楼层并且方向换为下降。而我这里虽然保留电梯仍为上升但没有修改请求的方向也就是,电梯在上升至该楼层后方向仍为上升,而该楼层请求还是下降不符合同向,不会在这一层开门处理,从而导致了死循环电梯不断上下浮动,在修改完后也是成功运行。但因为这个卡了我好久OVO。还有一点踩坑的就是对于给定的唯一一个测试样例了,如果一开始没有看见只考虑头部请求而去写的话那么将走进一个超级超级大的陷阱!因为你会发现,如果用完全使用LOOK算法,并且像现实里电梯一样还需要考虑时间调度那么按这种思路逻辑走下去写出来的代码测试一下,会发现:诶!测试样例没有问题提交一下吧。然后就是错误了,并且如果一直没有发现逻辑错误的话一直改下去会发现一直都是答案错误。嗯没错,我因为这个错了好几天也写了好几天,最后发现不需要考虑时间问题后恍然大悟了(哭笑)。

改进建议:

这次代码的改进很明显,逻辑部分应该修改更简洁。还有就是降低代码的复杂度和嵌套层次,大大增加代码的复用性。当然,条件分支也要减少,毕竟有一堆的if-else语句,别说别人了,我自己看都要看半天才能理解我到底写了个啥,逻辑是什么。不过这也有一定好处,至少体现了我的不可或缺性,毕竟没了我可能没什么人看得懂了。

 

 

题目集6单部电梯调度程序(类设计

目标:通过创建电梯类来模拟电梯的运行并且优先处理同方向的请求,以及在移动过程中处理顺路的请求.同时对非法请求和重复请求进行处理。

设计模式:生产者—消费者模式

算法:Look算法

设计与分析:

 Controller类:对电梯进行调度

    public Controller(elevator ElEvator, RequestQueue request)
    public void removeInterRequest()// 移除第一个内部请求,调整内部请求数组
    public void removesame()// 移除内部/外部请求中的重复项
    public void removeOutsideRequest()// 移除第一个外部请求,调整外部请求数组
    public void handlenointer()// 处理无内部请求时的外部请求停靠逻辑
    public void changedir()// 根据请求队列改变电梯运行方向
    public void process()// 主处理逻辑:循环处理请求直至队列为空
    public void handleCurrentFloor()// 处理当前楼层的请求
    public void move()// 移动电梯并打印当前楼层和方向
    public void ifDirectionChange()// 判断是否需要改变电梯运行方向

 Direction类:枚举类给定电梯的状态

 

UP, DOWN, IDLE

 

Elevator类:电梯类设定电梯的基本数据

 

public int currentFloor;
public Direction direction;
public State state;
public int maxFloor;
public int minFloor;
public elevator(int minFloor, int maxFloor) //构造函数给定电梯的最低最高楼层初始楼层和初始状态

 

ExternalRequest类:外部请求队列类,对外部请求进行处理

public int floor;
public Direction direction; public ExternalRequest(Integer floor, Direction direction)

RequestQueue类:对所有请求进行添加处理

public int internalRequestCount = 0;
public int[] internalRequests = new int[10005];
public int[][] ExternRequests = new int[10005][2]; public int ExternRequestCount = 0; public void getInterRequest(int floor) //添加内部请求队列public void getInterAndOutside(int floor, Direction direction) //添加外部请求队列

State类:枚举类给定电梯状态 

MOVING, STOPPED;

类图:

 

报表内容:

报表内容分析:

该代码有515行一共有149条语句而分支语句(if-else)占比28.2%对比第一次占比减少了一点而方法调用占比31%增加了不是相比之下,当然注释占比也提高了不少有31.3%。类平均方法只有1.17,方法比较少,说明我这个代码很符合单一职责原则可以避免因职责混杂导致代码臃肿。并且维护性和可测性也比较高,属于不错的代码。平均深度和平均复杂度都提高了不少,毕竟是第一次的迭代添加了不少内容。最大复杂度的类方法依旧是Main里的main,作为主函数需要调用许多的类复杂度高很正常,不过有点正常过头了,最大复杂度达到了12,需要重点关注。

踩坑心得:

这次大作业很遗憾并没有拿到满分,还有一个测试点无法通过。无论我怎么去测试怎么调整都无法通过,算是令我十分郁闷的一点。

 

这次作业只是在前一次的基础上添加了一个条件即对不合理请求的过滤和处理,这次有一个测试点无法通过的话反应了一个事实:那就是我的上一次代码有很大的缺陷,仅仅只是通过了测试点但没有考虑到所有可能发生的情况,以至于在进行迭代后我不得不去大量修改逻辑,这也算是吃了一个哑巴亏了只能怪我上一次作业太过于急功近利了只考虑是否通过测试点,而下一次的大作业可真的要先好好构思一番再进行动手了。

改进建议:

对于这次代码的改进地方,心里已经有了大概了。首先便是逻辑条理不够清晰,有太多的分支语句了这也算是一个老毛病了,对于特殊情况的判断只能通过不断添加if-else来进行解决,虽然这样很爽但是后面迭代时需要修改逻辑时那可真是火葬场了。其次便是main函数主函数这里了,复杂度过于高了可能需要考虑对其进行重构了来降低复杂度。还有就是减少嵌套,不断可以降低复杂度还可以大大减少运行的时间。当然也有一些地方可以进行优化,比如把存储队列的数组给修改为Queue或者LinkedList这样无论是存储请求还是删除请求都比数组要方便不少,也可以大大减少运行时间。

 

题目集7单部电梯调度程序(类设计-迭代)

目标:通过创建电梯类来模拟电梯的运行,但修改了请求的形式其余为先前的迭代

设计模式:生产者—消费者模式

算法:Look算法

设计与分析:

 Controller类:对电梯进行调度

 

public Controller(elevator ElEvator, RequestQueue request)
public void removeInterRequest()// 移除第一个内部请求,调整内部请求数组
public void removesame()// 移除内部/外部请求中的重复项
public void removeOutsideRequest()// 移除第一个外部请求,调整外部请求数组
public void process()// 主处理逻辑:循环处理请求直至队列为空
public void handleCurrentFloor()// 处理当前楼层的请求
public void move()// 移动电梯并打印当前楼层和方向
public void ifDirectionChange()// 判断是否需要改变电梯运行方向

 

Elevator类:电梯类设定电梯的基本数据

 

public int currentFloor;
public Direction direction;
public State state;
public int maxFloor;
public int minFloor;
public elevator(int minFloor, int maxFloor) //构造函数给定电梯的最低最高楼层初始楼层和初始状态

 

Direction类:枚举类给定电梯的状态

UP, DOWN, IDLE

State类:枚举类给定电梯状态 

MOVING, STOPPED;

RequestQueue类:对所有请求进行添加处理

public int internalRequestCount = 0;
public int[] internalRequests = new int[10005];
public int[][] ExternRequests = new int[10005][3];//使用3维数组存储外部请求信息分别是请求楼层,目标楼层和运行方向。
public int ExternRequestCount = 0;
public void getInterRequest(int floor)public void getInterAndOutside(Passenger passenger,int direction) 

Passenger类:乘客类,用于对外部乘客进行添加

private int sourceFloor;
private int destinationFloor;
public Passenger(int sourceFloor, int destinationFloor)public int getSourceFloor() public void setSourceFloor(int sourceFloor) 
public int getDestinationFloor() public void setDestinationFloor(int destinationFloor)

类图: 

 

 

报表内容:

 报表内容分析:

 这一次代码相较于第二次代码行数减少了许多,虽然是第二次的迭代但是逻辑上减少了不少判断。但即使这样代码分支数和调用方法数都增加了不少注释占比也从原来的31.3%下降到20.9%,不过每个类的方法降低了很多由1下降到了0.6代码的维护性和可测性大大提高了不少,不过每个方法的语句相较下多了不少,需要处理。和先前一致,最复杂的还是Main函数,我的main函数这一次相较于第二次,在添加过程中创建了Passenger的实例,用Passenger来把请求存储到队列之中故而复杂度提高了1。

 Passenger passenger = new Passenger(floor, destinationFloor);

与此同时,平均复杂度增加到了7.00。虽然这一次逻辑减少了但是复杂度反而提高了说明代码还有些地方需要修改简化去重。从图表中也可以看出来,我的平均最大复杂度和平均方法过高还是有问题需要修改的。

踩坑心得:

 这一次作业写的是比较快的,虽然我第二次作业没有完全通过,但代码却是由第二次代码修改而来,减少了一点逻辑。而这一次也有些地方卡了一小会,因为是在第二次代码上进行迭代修改,并且对请求方式进行了修改而我一开始是先搬用了第二次代码并对其添加方式进行了修改。一开始时对一些只是内部请求或者外部请求还是能够正常运行的,但是一旦混合起来就出现问题了。它无限循环了,也就是逻辑出错了。

如我在这里输入以上结果进行测试发现结果为

 在9,3层之间来回循环。按道理可能是逻辑出错了,因此我对处理电梯运行逻辑部分进行仔细查看修改测试并无发现问题,并且只有在这种混合情况下才会出现死循环问题,这让我百思不得其解。直到后面我在删除外部请求部分代码发现了问题

public void removeOutsideRequest() {
        if (requestqueue.ExternRequestCount > 0) {
            for (int i = 0; i < requestqueue.ExternRequestCount - 1; i++) {
                requestqueue.ExternRequests[i][0] = requestqueue.ExternRequests[i + 1][0];
                requestqueue.ExternRequests[i][1] = requestqueue.ExternRequests[i + 1][1];
            }
            requestqueue.ExternRequestCount--;
        }
    }

已知我是在外部请求部分使用了3维数组而我删除却只删除2维还有一个没有删除,因此导致方向不清晰从而死循环了。在将这个错误修改了问题就解决了,而我之前修改的代码也完全没有必要了,因为我用一开始的代码进行修改后发现也可以通过(痛苦)。关于这一题的话我也有一些心得。首先这一题虽然把输入方式由之前的<请求楼层数,请求方向>修改为<请求源楼层,请求目的楼层>,但我也只是在原来二维数组修改为三维,把第二个由之前存储方向(1为上升请求2为下降请求)改为存储目标楼层,而新增加的存储方向,这样我的判断逻辑部分就不需要大改了只需要修改一下添加部分还是比较方便的。

改进建议

对于我这一次代码,我想我还是需要对我代码的存储方式进行修改,数组固然好用但有时也会出现问题并且浑然不知,如删除部分如果少删除了一部分轻则导致一部分逻辑走不通,重则使整个逻辑都出现问题,而链表的删除方式就相比要方便不少。另外在这一次代码中对于电梯的状态我只有上升和下降和运行,停止状态根本没有用到,或许在以后运行当中需要显示电梯的实时状态,如运行中显示:Moving UP/DOWN,处理时显示:STOPPED IDLE ,那我就必须修改逻辑和使用STOPPED,IDLE使其输出正常。

 总结:

 这一轮大作业使我收益颇丰,首先是一开始的小作业题目,先让我们根据题目要求写出相应的单个类出来,接着下一次作业中又让我们根据题目创建出不同类出来并且类之间还需要有合作。当然考虑到我们第一次接触这种,还给出参考类图出来可以根据参考类图来创建类来。不只是教会我们创建类和调用不同类,其中还有一些课外的知识如蒙特卡洛求圆周率和身份证校验码的计算,这种知识扩展还是比较有用的无论是以后还是现在。小作业的难度上循序渐进,颇有一种入门到精通的感觉。而大作业,电梯调度问题。虽然难度有点大,但是如果你能够捋清楚其中的逻辑那么想要去解决还是没有什么问题的,其余的只是在特殊情况下欠缺考虑和处理。通过这一次作业,我也意识到自己的不足,首先我对单一职责原则理解不够,喜欢把方法一股脑堆进一个类里面,这不仅导致代码几乎不能复用,而且在面对多个类中也难以下手。我这种做法很明显是面向过程,而不是面对对象的。在以后编写代码之中我必须让自己做到解决一个问题时先分析问题给出类图再动手编写,虽然这个思维老师早已讲过,但只有这一次让我深刻个体到这种思维重要性。

posted on 2025-04-21 08:54  病故  阅读(133)  评论(0)    收藏  举报

导航