BUAA-OO-2021 第二单元总结

上这个课的一点点理解

进程

  • 不共享状态
  • 调度由操作系统完成
  • 有独立的内存空间(上下文切换的时候需要保存栈、cpu寄存器、虚拟内存、以及打开的相关句柄等信息,开销大)
  • 通讯主要通过信号传递的方式来实现(实现方式有多种,信号量、管道、事件等,通讯都需要过内核,效率低)

线程

  • 共享变量(解决了通讯麻烦的问题,但是对于变量的访问需要加锁)
  • 调度由操作系统完成(由于共享内存,上下文切换变得高效)
  • 一个进程可以有多个线程,每个线程会共享父进程的资源(创建线程开销占用比进程小很多,可创建的数量也会很多)
  • 通讯除了可使用进程间通讯的方式,还可以通过共享内存的方式进行通信(通过共享内存通信比通过内核要快很多)

协程

  • 调度完全由用户控制
  • 一个线程(进程)可以有多个协程
  • 每个线程(进程)循环按照指定的任务清单顺序完成不同的任务(当任务被堵塞时,执行下一个任务;当恢复时,再回来执行这个任务;任务间切换只需要保存任务的上下文,没有内核的开销,可以不加锁的访问全局变量)
  • 协程需要保证是非堵塞的且没有相互依赖
  • 协程基本上不能同步通讯,多采用异步的消息通讯,效率比较高

异步编程

  • 回调地狱函数
  • Promise
  • async/await

第一次作业

类图

classDiagram FahrstuhlImpact InuptThread Person PersonList LiftThread Strategy <-- StrategyRandom : implements Strategy <-- StrategyNight : implements StrategyRandom <-- StrategyMorning : inheritance class FahrstuhlImpact { main(String[]) } class InputThread { run() } class Person { int from int to int id moveIn() moveOut() isInLift() boolean isComplete() boolean costDistance(int) int } class PersonList { List~Person~ requests getInstance() PersonList addRequest(Person) getMainRequest(int, boolean) Person getNightTop() Person isEmpty() boolean canMoveOut(int) Person[] canMoveIn(int) Person[] } class LiftThread { Strategy currentStrategy int currentLayer int currentPeople isBusy() boolean isFull() boolean arrive() open() close() moveIn(Person) moveOut(Person) displace(PersonList) mutedTask(long, long, BooleanSupplier) boolean run() } class Strategy { <<interface>> decideDestination(LiftThread, PersonList) int decideDestinationFree(LiftThread, PersonList) int dispatchTraveller(LiftThread, PersonList) }

第一次作业中,创建了主线程、输入线程、电梯线程。其中主线程会等待,直至输入线程完毕并且电梯处于空闲状态,方可结束。输入线程在一开始会获取并设置电梯模式,电梯模式会有对应的Strategy(策略),其为一个接口,并且分别有三种class(类)实现之。Night模式下,遵从从上往下取的策略;Morning模式与Random模式相同;Random模式则是参考了去年的一位学长博客中的算法(传送门)。

单电梯主要策略是,将最近的可接入或可送出的人作为主请求,优先满足主请求。示意代码如下:

public Person getMainRequest(int liftLayer, boolean liftFull) {
    Stream<Person> xs = requests.stream();
    return (liftFull ? xs.filter(Person::isInLift) : xs)
        .min(Comparator.comparing(p -> 
        	Math.abs((p.isInLift() ? p.getTo() : p.getFrom()) - liftLayer))
        .orElse(null);
}
stateDiagram-v2 [*] --> 创建 创建 --> 决策 state 决策 { [*] --> 最近可接或可送的乘客 : 电梯未满 [*] --> 最近可送的乘客 : 电梯已满 最近可接或可送的乘客 --> 主请求 最近可送的乘客 --> 主请求 主请求 --> [*] } 决策 --> 执行 state 执行 { [*] --> 忙碌 : 有主请求 [*] --> 空闲 : 无主请求 空闲 --> [*] : 等待被其它线程唤醒 忙碌 --> 开门 : 当前楼层能满足主请求 开门 --> 乘客上下电梯 乘客上下电梯 --> 乘客上下电梯 : 当前楼层还存在主请求 乘客上下电梯 --> 关门 忙碌 --> 上行 : 主请求在当前楼层的上方 忙碌 --> 下行 : 主请求在当前楼层的下方 关门 --> [*] 上行 --> [*] 下行 --> [*] } 执行 --> 决策 决策 --> 销毁 : 主线程结束 销毁 --> [*]

在代码中只有单例模式PersonList(请求集合)会被synchronized,对尽量少的内容加锁以避免死锁。

策略参考得分:98.5963

时序图

sequenceDiagram FahrstuhlImpact->>+InputThread: begin InputThread InputThread->>Strategy: set Strategy InputThread->>PersonList: add request PersonList->>LiftThread: get request Strategy->>LiftThread: decide destination LiftThread->>LiftThread: reach destination InputThread->>PersonList: add request PersonList->>LiftThread: get request Strategy->>LiftThread: decide destination LiftThread->>LiftThread: reach destination InputThread-->>-FahrstuhlImpact: end InputThread LiftThread->>FahrstuhlImpact: mission clear

代码度量

OCavg = Average opearation complexity(平均操作复杂度)

OCmax = Maximum operation complexity(最大操作复杂度)

WMC = Weighted method complexity(加权方法复杂度)

CogC = Cognitive complexity(认知复杂度)

ev(G) = Essential cyclomatic complexity(基本圈复杂度)

iv(G) = Design complexity(设计复杂度)

v(G) = cyclonmatic complexity(圈复杂度)

  • 代码统计

    Source File Total Lines Source Code Lines Source Code Lines[%] Comment Lines Comment Lines[%] Blank Lines Blank Lines[%]
    FahrstuhlImpact.java 50 43 86% 0 0% 7 14%
    InputThread.java 40 36 90% 0 0% 4 10%
    LiftThread.java 174 147 84% 5 3% 22 13%
    Misc.java 4 3 75% 0 0% 1 25%
    Person.java 46 35 76% 1 2% 10 22%
    PersonList.java 67 57 85% 0 0% 10 15%
    Strategy.java 16 5 31% 8 50% 3 19%
    StrategyMorning.java 7 6 86% 0 0% 1 14%
    StrategyNight.java 23 20 87% 0 0% 3 13%
    StrategyRandom.java 19 16 84% 0 0% 3 16%
    Total 446 368 83% 14 3% 64 14%
  • 类复杂度

    class OCavg OCmax WMC
    FahrstuhlImpact 7.0 7.0 7.0
    InputThread 2.0 3.0 4.0
    StrategyNight 2.0 3.0 6.0
    LiftThread 1.94 7.0 33.0
    StrategyRandom 1.67 3.0 5.0
    PersonList 1.38 3.0 11.0
    Person 1.11 2.0 10.0
    StrategyMorning 1.0 1.0 1.0
    Total 77.0
    Average 1.75 3.625 8.56
  • 方法复杂度

    method CogC ev(G) iv(G) v(G)
    LiftThread.displace(PersonList) 17.0 4.0 7.0 8.0
    LiftThread.run() 15.0 3.0 7.0 9.0
    FahrstuhlImpact.main(String[]) 8.0 1.0 6.0 9.0
    LiftThread.mutedTask(long,long,BooleanSupplier) 7.0 4.0 3.0 5.0
    InputThread.run() 5.0 3.0 4.0 4.0
    LiftThread.muted(long) 5.0 3.0 3.0 4.0
    PersonList.getInstance() 3.0 1.0 1.0 3.0
    StrategyRandom.decideDestination(LiftThread,PersonList) 3.0 1.0 3.0 3.0
    StrategyNight.decideDestination(LiftThread,PersonList) 2.0 2.0 2.0 3.0
    ... ... ... ... ...
    Total 70.0 57.0 76.0 88.0
    Average 1.60 1.30 1.73 2.0

通过复杂度分析,发现LiftThread的方法较多,在设计上可以试图分散出去部分方法(如输出)。LiftThread.displace的认知复杂度偏高,则是在于内部对isFull的多次判断。FahrstuhlImpact的平均操作复杂度偏高,因为其main函数集成了初始化时间戳、创建电梯线、创建输入线程、判断是否结束等一系列操作,故代码略长。

BUG分析

本次作业暂无BUG

互测分析

似乎达成了某种共识,房间里没有发生一次hack

第二次作业

类图

classDiagram FahrstuhlImpact InuptThread Person PersonList LiftThread Strategy <-- StrategyRandom : implements Strategy <-- StrategyNight : implements Strategy <-- StrategyMorning : implements class FahrstuhlImpact { main(String[]) } class Person { int from int to int id moveIn() moveOut(int) isInLift() boolean isComplete() boolean } class LiftThread { Strategy currentStrategy int currentLayer int currentCarries LinkedList<Person> currentRequests int liftId long lastActionTime isBusy() boolean isFull() boolean arrive() open() close() moveIn(Person) moveOut(Person) mutedTask(long, BooleanSupplier) boolean run() workIdle(Integer) workBustle(Person) distribute(Person) getEstimate(Person) int selectEstimate(Person) } class Strategy { <<interface>> getScheme(LiftThread) Object preferLayer(LiftThread) int }

此次作业二在类上作出了精简,将读入进程整合到主进程中,同时删除PersonList。主进程也承担了分派器的功能。在作业一的基础上又略微优化了Morning的逻辑,因为作业一强测中MorningRandom策略相同取得了较低的性能分,虽然这次平均的性能分反而更低了。

同时添加了一个新的概念:偏向楼层。偏向楼层是指电梯处于空闲时偏向于停靠的楼层。比如现在有四部空闲的电梯,则他们的偏向楼层应当分别为[3,7,13,17]。因为在这样的分配下,任意一个新的请求产生都可以在3层内移动到请求的出发楼层,随机情况下能使耗时的方差变少。

为了保持简单,分派器的规则相当朴素。在单电梯与作业一策略相同的情况下,分派给最闲的那一部电梯,如果有多部已收揽请求数最少的电梯,则分派给最近的:

public void distribute(Person p) {
    currentRequests.add(p);
}

public int getEstimate(Person p) {
	return currentRequests.size() * 100 + Math.abs(p.getFrom() - currentLayer);
}

public static void selectEstimate(Person p) {
	LIFT_GROUP.values().stream()
        .min(Comparator.comparing(l -> l.getEstimate(p)))
        .ifPresent(l -> l.distribute(p));
}

当然,性能分是不怎么样的。不过优点大概就是......比较好实现。

策略参考得分:97.8086

时序图

sequenceDiagram FahrstuhlImpact->>+LiftThread-1: begin lift FahrstuhlImpact->>+LiftThread-2: begin lift FahrstuhlImpact->>LiftThread-1: dispatch request FahrstuhlImpact->>LiftThread-2: dispatch request Strategy->>LiftThread-1: get scheme Strategy->>LiftThread-2: get scheme LiftThread-1->>LiftThread-1: complete request LiftThread-2->>LiftThread-2: complete request FahrstuhlImpact->>+LiftThread-3: add lift FahrstuhlImpact->>LiftThread-3: dispatch request Strategy->>LiftThread-3: get scheme LiftThread-3->>LiftThread-3: complete request LiftThread-1->>-FahrstuhlImpact: end lift LiftThread-2->>-FahrstuhlImpact: end lift LiftThread-3->>-FahrstuhlImpact: end lift

代码度量

  • 代码统计

    Source File Total Lines Source Code Lines Source Code Lines[%] Comment Lines Comment Lines[%] Blank Lines Blank Lines[%]
    FahrstuhlImpact.java 66 57 86% 0 0% 9 14%
    LiftThread.java 293 235 80% 26 9% 32 11%
    Person.java 52 41 79% 0 0% 11 21%
    Strategy.java 5 4 80% 0 0% 1 20%
    StrategyMorning.java 31 28 90% 0 0% 3 10%
    StrategyNight.java 32 28 88% 0 0% 4 12%
    StrategyRandom.java 22 20 91% 0 0% 2 9%
    Total 501 413 82% 26 5% 62 12%
  • 类复杂度

    class OCavg OCmax WMC
    FahrstuhlImpact 12.0 12.0 12.0
    StrategyRandom 2.0 3.0 4.0
    StrategyMorning 1.67 3.0 5.0
    StrategyNight 1.67 3.0 5.0
    LiftThread 1.64 9.0 46.0
    Person 1.09 2.0 12.0
    Total 84.0
    Average 1.75 5.33 14.0
  • 方法复杂度

    method CogC ev(G) iv(G) v(G)
    LiftThread.workBustle(Person) 23.0 6.0 10.0 13.0
    FahrstuhlImpact.main(String[]) 11.0 3.0 7.0 11.0
    LiftThread.mutedTask(long,BooleanSupplier) 8.0 4.0 4.0 6.0
    LiftThread.workIdle(Integer) 8.0 3.0 3.0 5.0
    LiftThread.run() 6.0 1.0 5.0 5.0
    StrategyMorning.getScheme(LiftThread) 2.0 3.0 3.0 3.0
    ... ... ... ... ...
    Total 64.0 64.0 80.0 91.0
    Average 1.33 1.33 1.67 1.90

main函数较高的圈复杂度和平均操作复杂度,是因为承担了太多的职责,一份比较清晰的代码应当在main函数中放置尽量少的内容。此外LiftThread.workBustle的复杂度都偏高,这是本电梯运行的主要操作,因为嵌套了一定的lambda函数和if判断,故较为复杂。

BUG分析

本次作业在强测中并未出错。在互测阶段被检查出错误。

这是一个非常简单而低级的错误,希望后人不要再犯,以及以后不许再犯。本电梯在读入请求后,会分派给合适的电梯,电梯主循环发现存在请求就会被标记为忙碌状态。主线程的结束标志就是所有的电梯都处于空闲状态。但是如果读入的指令只有一条,但是这一条请求还没有来得及被电梯主循环判断,那么此时电梯依然还是处于空闲状态,而主线程发现电梯们全都是空闲的,所以提前结束了。而主线程结束,电梯线程也随之结束,整个程序没有输出就结束了,于是会造成Wrong Answer

解决的办法也比较简单,比如只改变一行的方式是,在读入完毕后Thread.sleep()一小会。在作业三中采用的解决方法是,在添加请求后立刻就将电梯标记为忙碌状态,这样就不会有问题了。

互测分析

本次房间中仅有一次hack,就将两份程序(包括我的)给叉掉了。也没想到什么好的查错办法。手头只有一个能生成随机数据并检查是否超时的多进程对拍程序,但检测周期较长,所以用处不大。

第三次作业

类图

classDiagram FahrstuhlImpact InuptThread Person PersonList LiftThread Strategy <-- StrategyRandom : implements class FahrstuhlImpact { main(String[]) disperseReq() globalReq() } class Person { int from int to int id moveIn() moveOut(int) isInLift() boolean isComplete() boolean } class LiftThread { Strategy currentStrategy AtomicInteger currentLayer AtomicInteger currentLoad int currentCarries List<Person> currentRequests int liftId long lastActionTime isBusy() boolean isFull() boolean arrive() open() close() moveIn(Person) moveOut(Person) mutedTask(long, BooleanSupplier) boolean run() workIdle(Integer) workBustle(Person) distribute(Person) getEstimate(Person) int selectEstimate(Person) } class Strategy { <<abstract>> getScheme(LiftThread) Object preferLayer(LiftThread) int AtomicInteger outerPreferA AtomicInteger outerPreferB AtomicInteger outerPreferC }

第三次作业对电梯的属性进行了设置。此外分派器的策略有所改变。getEstimate被赋予意义为一个电梯的评估值,评估值取决于电梯类型是否匹配请求、电梯属性、电梯的负载请求数、电梯完成请求的时间等,为各参数的加权求和(具体参数可以自行调整)。一个新请求会被分配给评估值最优的电梯。

当预估电梯的负载请求较多、请求跨越距离较长时,该请求会被拆分(最好不要出现A电梯忙C电梯闲,或者C电梯忙A电梯闲等类似情况)。比如,如果电梯的始末位置能被B电梯或C电梯抵达,则会将长距部分交给闲置的B电梯或C电梯运送。于是将请求分成两个子请求,在一个子请求完成时执行一个回调函数,添加下一个子请求。为防止下一个子请求提出时,相应的电梯却还傻乎乎地没到位,可以在一开始就将偏向楼层临时设置为中转楼层,以驱动空闲电梯前来接应。

此外删除了MorningNight两种策略。以及将部分int的成员改成了AtomicInteger,以保证原子操作。

策略参考得分:99.8782(值得注意的是,本次作业的性能分还与每个乘客的等待时长有关)。

可扩展性

感觉框架几乎没有可扩展性而言,但是三次作业确实是递进式地开发,每一次都用到了上个版本的内容。

时序图

与第二次作业相同,变动的只有策略,此处省略。

代码度量

  • 代码统计

    Source File Total Lines Source Code Lines Source Code Lines[%] Comment Lines Comment Lines[%] Blank Lines Blank Lines[%]
    FahrstuhlImpact.java 116 104 90% 0 0.0 12 10%
    LiftThread.java 298 264 86% 0 0.0 34 11%
    Person.java 79 62 78% 0 0.0 17 22%
    Strategy.java 52 44 85% 0 0.0 8 15%
    StrategyRandom.java 27 25 93% 0 0.0 2 7%
    Total 572 499 87% 0 0.0 73 13%
  • 类复杂度

    class OCavg OCmax WMC
    FahrstuhlImpact 7.67 11.0 23.0
    Strategy 2.75 8.0 11.0
    LiftThread 2.08 8.0 54.0
    StrategyRandom 2.0 3.0 4.0
    Person 1.06 2.0 18.0
    Total 110.0
    Average 2.12 6.4 22.0
  • 方法复杂度

    method CogC ev(G) iv(G) v(G)
    FahrstuhlImpact.disperseReq(Person) 27.0 1.0 10.0 14.0
    LiftThread.workBustle(Person) 19.0 3.0 10.0 12.0
    FahrstuhlImpact.globalReq() 13.0 1.0 5.0 5.0
    LiftThread.mutedTask(long,BooleanSupplier) 8.0 4.0 4.0 6.0
    LiftThread.workIdle(Integer) 8.0 3.0 3.0 5.0
    FahrstuhlImpact.main(String[]) 7.0 3.0 6.0 7.0
    Strategy.preferLayer(LiftThread) 7.0 7.0 4.0 7.0
    ... ... ... ... ...
    Total 111.0 69.0 99.0 119.0
    Average 2.13 1.33 1.90 2.29

主线程和电梯线程的复杂度在前几次作业中有阐述。这里的disperseReq的复杂度较高,其实是一个简易分派器,会判断多种电梯类型,故复杂度有所提升。preferLayer的复杂度有小部分增加在于对不同电梯偏向楼层的判断。

BUG分析

本次作业暂无BUG。

互测分析

并没有找到很好的hack门道,所以随意提交了一份50指令的长距数据,遂有一位同学RTLE

其实本次本人所在的互测房间聚集了16171819级的同学,并且富含RTLECTLEWA多种错误。没有把握好这个机会也挺遗憾的。

谈一谈其它方面

第一次作业中Night模式的最优策略是?

很抱歉,我也不知道最优策略怎么才能快速算得。但是希望下面的分析对你的思考有帮助,或者说聪明的读者已经知道了最优策略了?

首先需要明确的一点是,第一次作业中的Night模式,在一开始就将请求全部导入了,并且之后不会新增电梯。

一个确定状态的输入,应该会存在一个绝对的最优方案(可能不唯一)。

有一种朴素的想法是从上往下取,并且尽量让电梯满员。至于为什么这种方案比较优,因为相对于从下往上取,电梯取的总趟数是相同的,但是从上往下总可以找到每趟最高楼层对应比从下往上低的。但是其实这样的想法是naive的。

  • 并不是每趟取满员最优。考虑\(20\)层有\(5\)人到\(1\)层,\(19\)层有\(2\)人到\(1\)层。如果先在\(20\)层从上往下取\(6\)人为一趟,再在\(19\)层取\(1\)人为一趟,需要\(3\)次开门时间;而如果在\(20\)层取\(5\)人为一趟,\(19\)层取\(2\)人为一趟,只需要\(2\)次开门时间。
  • 并不是从上往下取最优。考虑\(20\)层有\(5\)人到\(1\)层,\(19\)层有\(6\)人到\(1\)层,\(18\)层有\(6\)人到\(1\)层,\(17\)层有\(6\)人到\(1\)层。总共要取\(4\)趟,停靠最高楼层情况相同,从上往下取一共要开门\(7\)次,而从下往上取只要开门\(4\)次。
  • n个人并不是⌈n/6⌉趟最优。考虑\(2\)层有\(5\)人到\(1\)层,\(3\)层有\(5\)人到\(1\)层,\(4\)层有\(5\)人到\(1\)层,\(5\)层有\(5\)人到\(1\)层,\(6\)层有\(4\)人到\(1\)层。总共\(24\)人,按\(\lceil \frac{24}{6}\rceil=4\)只需跑\(4\)趟,如果从上往下取,耗费的时间为:\((5×2+4×2+3×2+2×2)×0.4+8×0.4=36×0.4\)。但如果分五趟每次把一整层的人送完,耗费的时间为:\((5×2+4×2+3×2+2×2+1×2)×0.4+5×0.4=35×0.4\)。则后者更优。
  • 并不是取相邻的楼层最优。考虑\(20\)层有\(4\)人到\(1\)层,\(19\)层有\(5\)人到\(1\)层,\(18\)层有\(2\)人到\(2\)层,\(17\)层有\(1\)人到\(1\)层。如果从上往下尽可能取相邻的楼层,则第一趟最高楼层\(20\),取了\(20\)\(4\)人、\(19\)\(2\)人,第二趟最高楼层\(19\),取剩余的\(6\)人,开门共计\(5\)次。而如果一趟取\(20\)\(4\)人+\(18\)\(2\)人,另一趟取\(19\)\(5\)人+\(17\)\(1\)人,开门只需要\(4\)次。

总之,诸多事例表明,从上往下取\(6\)人并不是最优的。

考虑人数比较少的情况(\(\leq 16\)人)可以状压,用一个二进制数表示某个人在方案中取或不取(取则该二进制位为\(1\),不取则为\(0\))。

\(f(S)\)表示状态\(S\)下的最优耗时,则可以得到以下式子,其中popcount表示二进制位中\(1\)的个数。

\[f(S)=\begin{cases} (2×(S中乘客的最高楼层-1)+S中乘客楼层的种数)×0.4 &popcount(S)\leq 6 \\ \underset{x\in S,popcount(x)\leq 6}{min}{f(S-x)+f(x)} &else \end{cases} \]

于是\(n\)人时,状态数\(2^n\),子集枚举的复杂度\(\sum_{i=0}^{n}C_n^i2^{n-i}=3^n\)(但实际上只要枚举不超过\(6\)人的集合,即为\(\sum_{i=1}^{6}C_{n}^{i}2^{n-i}\)),在\(16\)人以内时估计可以在\(1\)秒内计算得出,但如果是\(30\)人或更多则CPU耗时会超时。

因为处于同一楼层的人可以认为等价,所以\(30\)\(20\)层理论状态数可以缩小到\(2^{10}\times 3^{10}\)。但是转移依然耗时。

不考虑炼金术的神经网络,我们可以用另外的人工智能算法——模拟退火(传送门)来实现简易、快速、较优的求解多元函数最值。

设定好温度\(T\)与温度变化率\(\Delta T\)后,随便以某个贪心算法的方案作为\(x\)(或者随机亦可)。方案以一个二维数组表示:第一维表示趟数,趟数取\(\lceil \frac{n}{6} \rceil\)\(\lceil \frac{n}{6} \rceil+1\);第二维表示每趟取的人的编号,这一维的数组长度不能超过\(6\);例如\([[1,4,5],[2,3]]\)是合法的。\(\Delta x\)则随机移动一个人到另一组方案中或交换两组方案中的一对人(合法情形下)。\(f\)定义与上述类似。随机若干次(比如一千次)后,对于贪心失效的特殊数据,往往能得到优于贪心的策略。

该方法也可以推广到除了Night模式的一般情形下。

可视化电梯?

以下用javascript写了一个模拟第三次作业的小demo,因为偷懒用setTimeout所以在过快的加速或者过多的请求时动画表现会有不符(应该用回调或者Promise会更严格),也是因为偷懒人物挤在一起不想布局了,凑合着可以玩一玩。

样例输入
[0.0]Random
[0.0]ADD-4-C
[0.0]1-FROM-3-TO-1
[0.0]2-FROM-4-TO-1
[0.0]3-FROM-5-TO-1

样例输出
[ 0.2380]ARRIVE-2-3
[ 0.4380]ARRIVE-2-2
[ 0.4400]ARRIVE-3-3
[ 0.4400]OPEN-3-3
[ 0.4430]IN-1-3-3
[ 0.6380]ARRIVE-2-1
[ 0.8400]ARRIVE-3-2
[ 0.8420]CLOSE-3-3
[ 1.0460]ARRIVE-2-3
[ 1.2400]ARRIVE-3-1
[ 1.2410]ARRIVE-4-2
[ 1.2480]ARRIVE-1-3
[ 1.2490]OPEN-1-3
[ 1.2500]OUT-1-1-3
[ 1.6480]ARRIVE-5-2
[ 1.6500]CLOSE-1-3
[ 1.6530]OPEN-5-2
[ 1.6560]IN-3-5-2
[ 1.8460]ARRIVE-4-1
[ 1.8490]OPEN-4-1
[ 1.8520]IN-2-4-1
[ 2.0560]CLOSE-5-2
[ 2.2520]CLOSE-4-1
[ 2.4590]ARRIVE-4-2
[ 2.8600]ARRIVE-3-2
[ 2.8660]ARRIVE-3-1
[ 3.2610]ARRIVE-2-2
[ 3.4750]ARRIVE-2-1
[ 3.6620]ARRIVE-1-2
[ 3.6620]OPEN-1-2
[ 3.6620]OUT-3-1-2
[ 4.0640]CLOSE-1-2
[ 4.0880]ARRIVE-1-1
[ 4.0880]OPEN-1-1
[ 4.0890]OUT-2-1-1
[ 4.4900]CLOSE-1-1

const iframe = document.createElement('iframe');
iframe.style.cssText = "width: 100%; height: 1402px;"
iframe.frameBorder = "no"
iframe.sandbox = "allow-same-origin allow-scripts"
iframe.srcdoc = '<!DOCTYPE html><html><head><meta charset="utf-8"><title>Visual Lift</title><link rel="stylesheet" href="https://blog-static.cnblogs.com/files/blogs/671148/bootstrap.min.css"><style>#lift-input,#lift-output{resize:none}.icon-play{max-height:18px;max-width:18px}.icon-lift>svg{max-height:50px;max-width:50px}.icon-person>svg{height:30px;width:20px;margin:0,auto}.icon-person{height:50px;font-size:12px;text-align:center}#surface>li{height:50px}#surface{position:absolute;top:0;bottom:0;left:100px;right:20px}.sprite{position:absolute;display:inline-block}.field{height:760px}.field li{height:38px}</style></head><body><div class="container"><div class="row mt-sm-3"><div class="col-sm"><div class="input-group mb-sm-3 px-sm-3"><div class="input-group-prepend"><span class="input-group-text">第七次作业的电梯输入</span></div><textarea id="lift-input" class="form-control" aria-label="第七次作业的电梯入"></textarea></div><div class="input-group mb-sm-3 px-sm-3"><div class="input-group-prepend"><span class="input-group-text">第七次作业的电梯输出</span></div><textarea id="lift-output" class="form-control" aria-label="第七次作业的电梯输出"></textarea></div><div class="input-group mb-sm-3 px-sm-3"><div class="input-group-prepend"><span class="input-group-text">输出相对于输入的偏移(秒)</span></div><input id="time-bias" type="text" class="form-control" placeholder="0.5(偏大也没关系)" aria-label="Time Bias"><div class="input-group-prepend"><span class="input-group-text">时间倍速缩放</span></div><input id="time-scale" type="text" class="form-control" placeholder="1.0(建议不要太小)"                        aria-label="Time Scale"><div class="input-group-append"><button id="lift-output-button" class="btn btn-outline-secondary" type="button" data-toggle="tooltip" data-placement="bottom" title="播放"><svg t="1619158932811" class="icon-play" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="14918" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M512 64c247.424 0 448 200.576 448 448S759.424 960 512 960 64 759.424 64 512 264.576 64 512 64z m-54.358 295C425.784 359 400 384.8 400 416.588V607.35c0 10.468 2.875 20.782 8.106 29.694 16.67 27.317 52.077 36.039 79.258 19.796l155.815-94.481c7.947-4.85 14.527-11.375 18.913-18.896 17.121-27.34 8.725-62.847-18.013-79.184l-155.814-96.28c-9.515-6.025-19.99-8.999-30.623-8.999z" fill="#333333" p-id="14919"></path></svg></button></div></div></div></div><div id="hint-panel" class="row mt-sm-3 mx-sm-3"></div><div class="row mt-sm-3 ml-sm-3 mr-sm-3"><div class="col-sm"><div class="field"><ul class="list-group"><li class="list-group-item">楼层20</li><li class="list-group-item">楼层19</li><li class="list-group-item">楼层18</li><li class="list-group-item">楼层17</li><li class="list-group-item">楼层16</li><li class="list-group-item">楼层15</li><li class="list-group-item">楼层14</li><li class="list-group-item">楼层13</li><li class="list-group-item">楼层12</li><li class="list-group-item">楼层11</li><li class="list-group-item">楼层10</li><li class="list-group-item">楼层9</li><li class="list-group-item">楼层8</li><li class="list-group-item">楼层7</li><li class="list-group-item">楼层6</li><li class="list-group-item">楼层5</li><li class="list-group-item">楼层4</li><li class="list-group-item">楼层3</li><li class="list-group-item">楼层2</li><li class="list-group-item">楼层1</li></ul><div id="surface"></div></div></div></div></div><script src="https://blog-static.cnblogs.com/files/blogs/671148/jquery.min.js"></\script></\script><script src="https://blog-static.cnblogs.com/files/blogs/671148/bootstrap.bundle.min.js"></\script><script>$(function(){$(\'[data-toggle="tooltip"]\').tooltip()});var timeBias=.5,timeScale=1;INPUT_PATTERN=/^\\[(?<time>.*?)\\](Night|Morning|Random|((?<guest_id>.*?)-FROM-(?<begin>.*?)-TO-(?<end>.*?))|(ADD-(?<lift_id>.*?)-(?<lift_type>A|B|C)))$/;OUTPUT_PATTERN=/^\\[(?<time>.*?)\\](?<command>.*?)(-(?<guest_id>.*?))?-(?<layer>.*?)-(?<lift_id>.*?)$/;PERSON_SVG=\'<svg t="1619186987819" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="18898" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M511.072256 466.204672c118.641664 0 214.750208-96.0256 214.750208-214.74816 0-118.55872-96.106496-214.699008-214.750208-214.699008-118.621184 0-214.687744 96.139264-214.687744 214.699008C296.384512 370.179072 392.451072 466.204672 511.072256 466.204672zM831.439872 931.946496l-2.240512-11.626496c-17.294336-74.71616-69.692416-251.526144-103.744512-294.557696-45.973504-84.278272-177.14176-80.251904-177.14176-80.251904L444.234752 545.5104c0 0-96.888832-3.448832-152.369152 76.327936-29.63968 42.602496-75.691008 206.8224-94.978048 289.319936-3.38944 12.862464-4.499456 20.584448-4.499456 20.584448-7.804928 30.564352 11.604992 55.497728 43.114496 55.497728l553.481216 0C820.492288 987.242496 839.635968 962.348032 831.439872 931.946496z" p-id="18899"></path></svg>\';LIFT_SVG=\'<svg t="1619214410468" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="31950" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M524.461822 379.39282c22.960862 0 41.565331-17.403369 41.565331-38.873356 0-21.467941-18.605492-38.858006-41.565331-38.858006-22.945512 0-40.393597 17.389042-40.393597 38.858006C484.068225 361.989451 501.51631 379.39282 524.461822 379.39282zM192.214455 938.541653l88.888018 0L281.102474 79.319533l-88.888018 0L192.214455 938.541653zM503.324566 212.6901l29.614671 0L532.939238 103.854323l59.245716 41.172725L592.184954 115.920126l-74.044354-51.458999-74.045377 51.458999 0 29.106922 59.230366-41.172725L503.32559 212.6901zM325.547506 775.585257l385.184142 0L710.731648 242.274906 325.547506 242.274906 325.547506 775.585257zM355.177528 271.90269l325.925122 0 0 474.05376L355.177528 745.95645 355.177528 271.90269zM755.175657 79.319533l0 859.222119 88.889042 0L844.064699 79.319533 755.175657 79.319533zM578.411712 392.268058 453.093523 392.268058c0 0-18.2289-2.155083-23.928949 12.96222l0 117.254517c0 0 0.49223 12.006452 13.165374 12.006452 0 0 14.105831 0.072655 14.105831-12.672624l0-70.917166c0 0 7.826566-16.131399 13.671931 0.810459l0.071634 251.465219c0 0-0.491207 12.890588 13.179701 12.890588l14.163138 0.202615c0 0 13.383347-0.146333 13.383347-13.093203L510.905529 586.459852c0 0 0.506557-6.611581 7.623944-6.611581 0 0 6.264937 2.357697 6.264937 6.885827l0 117.529787c0 0 0 11.805884 13.670908 11.805884l13.744589 0c0 0 13.671931-0.809435 13.671931-11.805884L565.881837 450.700889c0 0 4.919234-14.973016 13.671931-0.202615l0.448226 71.843258c0 0-3.182613 12.428054 14.092527 12.428054 12.991404 0 12.587182-11.877515 12.587182-11.877515l0.144292-117.659747C606.824972 405.230278 600.069852 392.268058 578.411712 392.268058zM532.939238 805.170063l-29.614671 0 0 108.834754-59.230366-41.172725 0 29.107945 74.045377 51.457975 74.044354-51.457975 0-29.107945-59.245716 41.172725L532.938214 805.170063z" p-id="31951"></path></svg>\';function hintClear(){$("#hint-panel").html("")}function hintWarning(content){$("#hint-panel").append($(`<div class="alert alert-warning" role="alert">${content}</div>`))}function hintError(content){$("#hint-panel").append($(`<div class="alert alert-danger" role="alert">${content}</div>`))}function getPosition(col,row){return{left:`${16*(col-1)}%`,top:`${5*(20-row)}%`}}function Person(_id,_from,_to){this.id=_id;this.from=_from;this.to=_to;this.lift=null;this.people[_id]=this;this.instance=$(`<div class="sprite icon-person">${_id}<br>${PERSON_SVG}</div>`);this.instance.hide();$("#surface").append(this.instance);this.script={}}Person.prototype.people={};Person.prototype.create=function(time){const _this=this;_this.script[time*1e3-200*timeScale]=()=>{_this.instance.css(getPosition(1,_this.from));_this.instance.fadeIn(200*timeScale)}};Person.prototype.moveIn=function(time,lift){const _this=this;_this.script[time*1e3]=()=>{if(_this.lift!==null||lift.layer!==_this.from||!lift.validLayer(_this.from)||!lift.door||Object.keys(lift.carries).length==lift.limit||lift.carries[_this.id]){throw new Error(`电梯(${lift.id})无法接收乘客(${_this.id})`)}_this.instance.animate(getPosition(lift.serial+1,lift.layer),200*timeScale);_this.lift=lift;lift.carries[_this.id]=_this}};Person.prototype.moveOut=function(time){const _this=this;_this.script[time*1e3]=()=>{const layer=_this.lift.layer;if(_this.lift===null||!_this.lift.validLayer(layer)||!_this.lift.door){throw new Error(`乘客(${_this.id})无法出电梯`)}_this.from=layer;_this.instance.animate(getPosition(1,layer),200*timeScale);if(!_this.lift.carries[_this.id]){throw new Error(`电梯中不存在此人(${_this.id})`)}delete _this.lift.carries[_this.id];_this.lift=null;if(_this.from==_this.to){_this.instance.fadeOut(200*timeScale)}}};function Lift(_id,_type){this.id=_id;this.type=_type;this.lifts[_id]=this;this.serial=Object.keys(this.lifts).length;this.carries={};this.layer=1;this.door=false;switch(_type){case"A":this.moveTime=600;this.limit=8;this.validLayer=x=>1<=x&&x<=20;break;case"B":this.moveTime=400;this.limit=6;this.validLayer=x=>1<=x&&x<=20&&(x&1)===1;break;case"C":this.moveTime=200;this.limit=4;this.validLayer=x=>1<=x&&x<=3||18<=x&&x<=20;break}this.instance=$(`<div class="sprite icon-lift">${LIFT_SVG}</div>`);this.instance.css(getPosition(this.serial+1,1));this.instance.hide();$("#surface").append(this.instance);this.script={}}Lift.prototype.lifts={};Lift.prototype.create=function(time){const _this=this;_this.script[time*1e3-200*timeScale]=()=>{_this.instance.css(getPosition(_this.serial+1,1));_this.instance.fadeIn(200*timeScale)}};Lift.prototype.arrive=function(time,layer){const _this=this;if(layer<1||layer>20){throw new Error("电梯层数有误")}_this.script[time*1e3-_this.moveTime*timeScale]=()=>{if(Math.abs(_this.layer-layer)!==1||_this.door){throw new Error(`电梯${_this.id}移动异常`)}_this.layer=layer;const pos=getPosition(this.serial+1,layer);_this.instance.animate(pos,{speed:_this.moveTime*timeScale,queue:false});for(const key in _this.carries){const p=_this.carries[key];if(p)p.instance.animate(pos,{speed:_this.moveTime*timeScale,queue:false})}}};Lift.prototype.open=function(time){const _this=this;_this.script[time*1e3]=()=>{if(_this.door||!_this.validLayer(_this.layer)){throw new Error(`电梯${_this.id}开门异常`)}_this.door=true}};Lift.prototype.close=function(time){const _this=this;_this.script[time*1e3]=()=>{if(!_this.door||!_this.validLayer(_this.layer)){throw new Error(`电梯${_this.id}关门异常`)}_this.door=false}};function parseIn(){try{const input=$("#lift-input").val();const lines=input.split("\\n").map(s=>s.trim()).filter(s=>s!=="");const linesv=lines.filter(s=>INPUT_PATTERN.test(s));if(lines.length!==linesv.length){hintWarning("部分输入语句不符合格式")}for(const line of linesv){const g=INPUT_PATTERN.exec(line).groups;const[time,gid,be,ed,lid,ltype]=[parseFloat(g.time),parseInt(g.guest_id),parseInt(g.begin),parseInt(g.end),parseInt(g.lift_id),g.lift_type];const cmd=g.guest_id?"GUEST":g.lift_id?"ADD":"MODE";const timeCurrent=time*timeScale;switch(cmd){case"GUEST":if(Number.isNaN(gid)||Number.isNaN(be)||Number.isNaN(ed)||!(1<=be&&be<=20)||!(1<=ed&&ed<=20)){throw new Error("乘客数字错误")}const p=new Person(gid,be,ed);p.create(timeCurrent);break;case"ADD":if(Number.isNaN(lid)){throw new Error("电梯数字错误")}const l=new Lift(lid,ltype);l.create(timeCurrent);break;default:}}}catch(e){hintError(e);return false}return true}function parseOut(){try{const input=$("#lift-output").val();const lines=input.split("\\n").map(s=>s.trim()).filter(s=>s!=="");const linesv=lines.filter(s=>OUTPUT_PATTERN.test(s));if(lines.length!==linesv.length){hintWarning("部分输出语句不符合格式")}for(const line of linesv){const g=OUTPUT_PATTERN.exec(line).groups;const[time,cmd,gid,layer,lid]=[parseFloat(g.time),g.command,parseInt(g.guest_id),parseInt(g.layer),parseInt(g.lift_id)];if(Number.isNaN(time)||Number.isNaN(lid)||(cmd==="IN"||cmd==="OUT")&&Number.isNaN(gid)){throw new Error("解析数字时非法")}const l=Lift.prototype.lifts[lid];if(!l){throw new Error("电梯不存在")}const p=Person.prototype.people[gid];if(cmd==="IN"||cmd==="OUT"){if(!p){throw new Error("顾客不存在")}}const timeCurrent=(time+timeBias)*timeScale;switch(cmd){case"ARRIVE":l.arrive(timeCurrent,layer);break;case"OPEN":l.open(timeCurrent-.05);break;case"CLOSE":l.close(timeCurrent-.05);break;case"IN":p.moveIn(timeCurrent,l);break;case"OUT":p.moveOut(timeCurrent);break;default:}}}catch(e){hintError(e);return false}return true}function simulate(){try{for(let key in Lift.prototype.lifts){let lift=Lift.prototype.lifts[key];Object.keys(lift.script).sort().forEach(k=>{setTimeout(lift.script[k],k)})}for(let key in Person.prototype.people){let person=Person.prototype.people[key];Object.keys(person.script).sort().forEach(k=>{setTimeout(person.script[k],k)})}}catch(e){hintError(e);return false}}function visual(){$("#surface").html("");Lift.prototype.lifts={};Person.prototype.people={};new Lift(1,"A").create(0);new Lift(2,"B").create(0);new Lift(3,"C").create(0);timeBias=$("#time-bias").val();timeBias=timeBias?parseFloat(timeBias):Number.NaN;if(Number.isNaN(timeBias)){timeBias=.5}timeScale=$("#time-scale").val();timeScale=timeScale?parseFloat(timeScale):Number.NaN;if(Number.isNaN(timeScale)){timeScale=1}hintClear();if(parseIn()&&parseOut()){simulate()}}$(document).ready(function(){$("#lift-output-button").click(visual)});</\script></\body></\html>'
document.querySelector('lift-demo').appendChild(iframe);

单元总结

这一单元关于线程安全层次化设计确实比较有挑战性,调试死锁的BUG以及如何合理地设计类是这一单元的特色。大概的心理历程:“那就代表我可以跑第一”→“因为我们是学算法的”→“说,我会求得最优策略的一切”→“重铸电梯荣光,我辈义不容辞”→“能运行就算成功”。在心态上也会趋于平缓,而不是去像解一道算法题一样锱铢必较,而是沉稳心细,能把握住主要内容,不浪费时间在不必要的数学意义的最优上。

整体上,我认为虽然电梯是祖传题目了,各方面有继续打磨的机会,但依然有比较重大的意义。结合操作系统等课程,可以加深对多线程的理解,多多地提早地踩坑解决坑,以加强学生的实力,提高思考、编程、解决问题的能力。

posted @ 2021-04-25 13:53  buaa-shy  阅读(177)  评论(1编辑  收藏  举报