记得刚开始学Java时用的是《Java大学教程》,这本书每章都在介绍一点电梯程序的样子,加上上班天天都要坐电梯,突发奇想想要做一个模拟电梯程序。
一开始没想清楚电梯程序的运行方式,但是知道几个关键表现。
- 电梯有静止的时候
- 电梯往往停在最后请求的楼层,也有最后静止在底层(1楼)的,这相当于电梯在没有请求时自己按了一下1
- 电梯存在插队现象,即电梯在1楼,A在8楼先按,B在4楼后按,如果电梯没到4楼的话,后按的人先进
其实还有一个关键点,一般电梯都是有向上向下提示的,电梯上行时,A在8楼按下,B在4楼按下的话,B是不会插队的。不过如果把这个也考虑在内的话问题会复杂很多,因为操作电梯会有两步:指示方向和指出具体楼层。为了简化问题,我实际考虑每个楼层只有一个按钮,不指示方向,电梯内仍旧有具体到达楼层的按钮。这样的电梯可能有点怪,但也可以用。
电梯问题的重点个人认为在两块地方,一块是接受指令时,第二块是每层楼运行时。
能够想见,接受指令时决定了目标楼层(nextFloor),最简单的情况,电梯一开始就是静止的,那么第一次指令就决定了目标楼层。插队现象也不难理解,如果电梯的当前楼层和目标楼层中间出现了请求,那么电梯的目标楼层就变成了中间这层的请求。考虑到电机运行速度,可能要计算当前楼层加一或者加二以上的范围,否则就可能有安全问题。
每层楼运行时,如果没有新请求,那么电梯会往一个方向(direction)运行,比如上行(up),下行(down),直到抵达目标楼层。抵达之后的操作就是根据现在的方向寻找下一个目标楼层。比如现在上行,到达4层,现在6层有新请求,3层也有请求。那么电梯会往6层去,直到抵达最高层或者上层再没有新请求,再往下走。下行的话正好相反,先往下找请求,直到底层或者没有再往上找。
一句话来说,电梯的核心算法就是如何动态确定下一个目标楼层。
这个算法刚才其实已经差不多说得差不多了。然后是具体需要的参数,个人认为当前楼层(currentFloor),目标楼层(nextFloor),请求楼层(requests,一个BitSet,位集合),方向(direction),状态(status)就差不多了。以下是模拟代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
import java.util.BitSet; import java.util.Random; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class Elevator implements Runnable { enum Direction { UP, DOWN; } // three directions, none means stop private Direction direction = null; enum Status { WAITING, OPEN, CLOSE, RUNNING; } private Status status = Status.WAITING; private static final int MIN_FLOOR = 1; private static final int NO_SUCH_FLOOR = –1; private int currentFloor = 1; // next request floor may be no floor when stop private int nextFloor = NO_SUCH_FLOOR; private final BitSet requests; /** * Create with floor count. * Floor starts from one. e.g 10 floors means 1..10. * * @param floors */ public Elevator(int floors) { this.requests = new BitSet(floors); } /** * Receive floor request. * Set next floor directly when stop, or try to insert next floor * if current floor + 1 < request floor < original next floor. * * @param floorNumber floor number */ public void receiveRequest(int floorNumber) { requests.set(floorNumber); System.out.println(String.format(“get floor request [%d]”, floorNumber)); if (nextFloor == NO_SUCH_FLOOR) { setNextFloor(floorNumber); } else if ((currentFloor + 1 <= floorNumber) && floorNumber < nextFloor) { nextFloor = floorNumber; } } private void setNextFloor(int floorNumber) { if (floorNumber == NO_SUCH_FLOOR) { direction = null; nextFloor = NO_SUCH_FLOOR; status = Status.WAITING; } else { direction = currentFloor < floorNumber ? Direction.UP : Direction.DOWN; nextFloor = floorNumber; status = Status.RUNNING; } } private void openAndCloseDoor() { status = Status.OPEN; System.out.println(“open door”); status = Status.CLOSE; System.out.println(“close door”); } /** * @see java.lang.Runnable#run() */ public void run() { // return if not running, may be opening and closing door if (status != Status.RUNNING) return; // test if reach requested floor // if reached, open and close door, find next request floor testIfReachRequestFloor(); // if no next floor found, don’t move if (nextFloor == NO_SUCH_FLOOR) return; // or simulate moving currentFloor += (direction == Direction.UP ? 1 : –1); // print status itself System.out.println(this); } private void testIfReachRequestFloor() { if (currentFloor != nextFloor) return; System.out.println(“reach floor “ + currentFloor); openAndCloseDoor(); requests.clear(currentFloor); setNextFloor(findNextRequestFloor()); } private int findNextRequestFloor() { // when move upward, find upward then downward if (direction == Direction.UP) { int nextFloorUpward = findRequestFloorUpward(currentFloor + 1); if (nextFloorUpward != NO_SUCH_FLOOR) return nextFloorUpward; return findRequestFloorDownward(currentFloor – 1); } // must be down // find downward then upward int nextFloorDownward = findRequestFloorDownward(currentFloor – 1); if (nextFloorDownward != NO_SUCH_FLOOR) return nextFloorDownward; return findRequestFloorUpward(currentFloor + 1); } private int findRequestFloorUpward(int start) { for (int i = start; i < requests.size(); i++) { if (requests.get(i)) return i; } return NO_SUCH_FLOOR; } private int findRequestFloorDownward(int start) { for (int i = start; i >= MIN_FLOOR; i—) { if (requests.get(i)) return i; } return NO_SUCH_FLOOR; } @Override public String toString() { return String .format( “Elevator [direction=%s, status=%s, currentFloor=%s, nextFloor=%s, requests=%s]”, direction, status, currentFloor, nextFloor, requests); } public static void main(String[] args) throws InterruptedException { int floors = 10; Elevator elevator = new Elevator(floors); // 1..10 ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1); executorService.scheduleAtFixedRate(elevator, 0, 1, TimeUnit.SECONDS); Random random = new Random(); for (int i = 0; i < floors; i++) { elevator.receiveRequest(random.nextInt(floors – 1) + 1); Thread.sleep(1000L); } } } |
实际执行结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
get floor request [5] Elevator [direction=UP, status=RUNNING, currentFloor=2, nextFloor=5, requests={5}] Elevator [direction=UP, status=RUNNING, currentFloor=3, nextFloor=5, requests={5}] get floor request [6] Elevator [direction=UP, status=RUNNING, currentFloor=4, nextFloor=5, requests={5, 6}] get floor request [5] Elevator [direction=UP, status=RUNNING, currentFloor=5, nextFloor=5, requests={5, 6}] get floor request [1] reach floor 5 open door close door Elevator [direction=UP, status=RUNNING, currentFloor=6, nextFloor=6, requests={1, 6}] get floor request [7] reach floor 6 open door close door Elevator [direction=UP, status=RUNNING, currentFloor=7, nextFloor=7, requests={1, 7}] get floor request [9] reach floor 7 open door close door Elevator [direction=UP, status=RUNNING, currentFloor=8, nextFloor=9, requests={1, 9}] get floor request [2] Elevator [direction=UP, status=RUNNING, currentFloor=9, nextFloor=9, requests={1, 2, 9}] get floor request [9] reach floor 9 open door close door Elevator [direction=DOWN, status=RUNNING, currentFloor=8, nextFloor=2, requests={1, 2}] get floor request [9] Elevator [direction=DOWN, status=RUNNING, currentFloor=7, nextFloor=2, requests={1, 2, 9}] get floor request [6] Elevator [direction=DOWN, status=RUNNING, currentFloor=6, nextFloor=2, requests={1, 2, 6, 9}] Elevator [direction=DOWN, status=RUNNING, currentFloor=5, nextFloor=2, requests={1, 2, 6, 9}] Elevator [direction=DOWN, status=RUNNING, currentFloor=4, nextFloor=2, requests={1, 2, 6, 9}] Elevator [direction=DOWN, status=RUNNING, currentFloor=3, nextFloor=2, requests={1, 2, 6, 9}] Elevator [direction=DOWN, status=RUNNING, currentFloor=2, nextFloor=2, requests={1, 2, 6, 9}] reach floor 2 open door close door Elevator [direction=DOWN, status=RUNNING, currentFloor=1, nextFloor=1, requests={1, 6, 9}] reach floor 1 open door close door Elevator [direction=UP, status=RUNNING, currentFloor=2, nextFloor=6, requests={6, 9}] Elevator [direction=UP, status=RUNNING, currentFloor=3, nextFloor=6, requests={6, 9}] Elevator [direction=UP, status=RUNNING, currentFloor=4, nextFloor=6, requests={6, 9}] Elevator [direction=UP, status=RUNNING, currentFloor=5, nextFloor=6, requests={6, 9}] Elevator [direction=UP, status=RUNNING, currentFloor=6, nextFloor=6, requests={6, 9}] reach floor 6 open door close door Elevator [direction=UP, status=RUNNING, currentFloor=7, nextFloor=9, requests={9}] Elevator [direction=UP, status=RUNNING, currentFloor=8, nextFloor=9, requests={9}] Elevator [direction=UP, status=RUNNING, currentFloor=9, nextFloor=9, requests={9}] reach floor 9 open door close door |
感觉和实际电梯有点像,就是没有动画演示。
如果每个楼层的按钮有方向的话,其实重点改造位置还是在findNextRequestFloor,以及在receiveRequest的操作,数据结构上原先的requests(BitSet)可能不够,要分成上行请求/下行请求等。具体这里就不实现了。