Project P033

Elevator

One lift over floors 0 to 9: people call it from a floor to go to another, time moves on in steps, and the lift keeps its direction while there is a reason to go on and turns round when there is none. Statistics show how long people waited, and the same calls can be run again by a lift that takes one passenger at a time, to compare.

3 modules · 2 recorded sessionstext-menu UI in the terminalupdated 2026-10-10

Every screen below was recorded under CPython. When this page was built, the EML interpreter replayed each session from the same input and printed the same bytes.

About

One lift serves floors 0 to 9. People call it from a floor to go to another; time moves on in steps - one floor of travel, or one stop with the doors open - and the screen shows each move and each stop with who got out and who got in. The lift follows the usual rule for a single car (collective control). The statistics show how long people waited for the lift and how long their whole trip took, and the same calls can be run again from the start by a lift that takes one passenger at a time, in call order, to compare.

  • main.eml - the menu, the questions and their checks, stepping and running on
  • lift.eml - the rules: one step of time with the direction rule or one passenger at a time, and a whole run of a list of calls
  • report.eml - the steps as lines, the building, the statistics and the comparison table

How each part works:

  • One step with the direction rule: riders whose floor this is get out; the lift keeps its direction while there is a reason to go on - a rider going further that way, a call from a floor further that way, or someone waiting here who wants to go that way. With no reason ahead but one behind, it turns round; with none at all, it goes idle, and an idle lift heads for the earliest call. Whoever waits at the floor and wants to go the lift's way gets in. A stop to let people out or in takes the step.
  • So nobody is carried past their floor, and the lift never leaves behind someone who wants to go its way. Someone who wants the other way waits until it comes back: in the basic session P4 waits at floor 7 to go down while the lift goes past on its way up to 8 and 9, and gets in on the way back.
  • The corpus case elevator-simulator moves one floor per step towards each request of a queue in turn. That is the one-at-a-time lift here, kept for the comparison: the same calls, at the same times, are run from t=0 by both lifts.
  • Times are whole steps: the wait runs from the call to getting in, the trip from the call to getting out. Averages are worked out in whole numbers and shown to one decimal place, halves rounded up; on a tie for the longest, the lowest passenger number is named.

What is checked: floors from 0 to 9; a destination other than the floor the caller is on; at most 20 calls in a run; 1 to 20 steps at a time. An empty answer cancels.

Sessions: sessions/basic.in has four people call at t=0 - from the ground floor up to 8, from 3 up to 6, from 5 up to 9 and from 7 down to 2; after four steps a fifth calls from floor 1, behind the lift. It shows the building, finishes the run, and shows the statistics and the comparison: an average wait of 9.8 steps with the direction rule, against 19.6 one at a time. sessions/bad-input.in gives menu choices 0 and x; asks for statistics, the comparison and the finish before any call; asks for 0, 21 and abc steps before three idle ones; gives floors 10 and x and the same floor twice; then makes twenty calls at once - two or three on most floors, both ways - and a twenty-first that is refused; shows the building, finishes (an average wait of 14.1 against 88.2 one at a time) and shows the statistics.

Built on the verified corpus case elevator-simulator (a lift that moves one floor per step towards each request of a queue in turn).

Recorded sessions

What the screen shows while someone uses the program. Each typed line appears after its prompt, the way a terminal shows it.

bad-input

interpreter: byte-equal
== Elevator ==
One lift, floors 0 to 9. A step of time is one floor of travel or one stop
with the doors open; finish runs on until everyone has arrived.

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=0 choice> 0
Pick a number from 1 to 7.

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=0 choice> x
Pick a number from 1 to 7.

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=0 choice> 5
Nobody has called the lift yet.

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=0 choice> 6
Nobody has called the lift yet.

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=0 choice> 3
Nobody is waiting or riding.

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=0 choice> 2
how many steps (1 to 20)> 0
Type a number from 1 to 20.
how many steps (1 to 20)> 21
Type a number from 1 to 20.
how many steps (1 to 20)> abc
Type a number from 1 to 20.
how many steps (1 to 20)> 3
t=0-2  idle at floor 0

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=3 choice> 1
from floor> 10
Type a floor from 0 to 9.
from floor> x
Type a floor from 0 to 9.
from floor> 
Cancelled.

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=3 choice> 1
from floor> 4
to floor> 4
That is the floor they are on.
to floor> 
Cancelled.

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=3 choice> 1
from floor> 4
to floor> 4
That is the floor they are on.
to floor> 9
P1 waits at floor 4 to go up to 9 (t=3).

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=3 choice> 1
from floor> 0
to floor> 9
P2 waits at floor 0 to go up to 9 (t=3).

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=3 choice> 1
from floor> 9
to floor> 0
P3 waits at floor 9 to go down to 0 (t=3).

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=3 choice> 1
from floor> 2
to floor> 7
P4 waits at floor 2 to go up to 7 (t=3).

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=3 choice> 1
from floor> 7
to floor> 2
P5 waits at floor 7 to go down to 2 (t=3).

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=3 choice> 1
from floor> 5
to floor> 6
P6 waits at floor 5 to go up to 6 (t=3).

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=3 choice> 1
from floor> 6
to floor> 5
P7 waits at floor 6 to go down to 5 (t=3).

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=3 choice> 1
from floor> 1
to floor> 8
P8 waits at floor 1 to go up to 8 (t=3).

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=3 choice> 1
from floor> 8
to floor> 1
P9 waits at floor 8 to go down to 1 (t=3).

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=3 choice> 1
from floor> 3
to floor> 4
P10 waits at floor 3 to go up to 4 (t=3).

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=3 choice> 1
from floor> 4
to floor> 3
P11 waits at floor 4 to go down to 3 (t=3).

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=3 choice> 1
from floor> 0
to floor> 1
P12 waits at floor 0 to go up to 1 (t=3).

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=3 choice> 1
from floor> 9
to floor> 8
P13 waits at floor 9 to go down to 8 (t=3).

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=3 choice> 1
from floor> 2
to floor> 3
P14 waits at floor 2 to go up to 3 (t=3).

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=3 choice> 1
from floor> 7
to floor> 6
P15 waits at floor 7 to go down to 6 (t=3).

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=3 choice> 1
from floor> 5
to floor> 0
P16 waits at floor 5 to go down to 0 (t=3).

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=3 choice> 1
from floor> 4
to floor> 9
P17 waits at floor 4 to go up to 9 (t=3).

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=3 choice> 1
from floor> 6
to floor> 9
P18 waits at floor 6 to go up to 9 (t=3).

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=3 choice> 1
from floor> 3
to floor> 0
P19 waits at floor 3 to go down to 0 (t=3).

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=3 choice> 1
from floor> 8
to floor> 2
P20 waits at floor 8 to go down to 2 (t=3).

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=3 choice> 1
That makes 20 calls, the most for one run.

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=3 choice> 4
 9 |    | P3 -> 0, P13 -> 8
 8 |    | P9 -> 1, P20 -> 2
 7 |    | P5 -> 2, P15 -> 6
 6 |    | P7 -> 5, P18 -> 9
 5 |    | P6 -> 6, P16 -> 0
 4 |    | P1 -> 9, P11 -> 3, P17 -> 9
 3 |    | P10 -> 4, P19 -> 0
 2 |    | P4 -> 7, P14 -> 3
 1 |    | P8 -> 8
 0 |[ 0]| P2 -> 9, P12 -> 1
t=3: the lift is at floor 0, idle, empty.

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=3 choice> 2
how many steps (1 to 20)> 
Cancelled.

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=3 choice> 3
t=3  floor 0 - in: P2 (to 9), P12 (to 1)
t=4  up to 1
t=5  floor 1 - out: P12 (waited 0, rode 2); in: P8 (to 8)
t=6  up to 2
t=7  floor 2 - in: P4 (to 7), P14 (to 3)
t=8  up to 3
t=9  floor 3 - out: P14 (waited 4, rode 2); in: P10 (to 4)
t=10  up to 4
t=11  floor 4 - out: P10 (waited 6, rode 2); in: P1 (to 9), P17 (to 9)
t=12  up to 5
t=13  floor 5 - in: P6 (to 6)
t=14  up to 6
t=15  floor 6 - out: P6 (waited 10, rode 2); in: P18 (to 9)
t=16  up to 7
t=17  floor 7 - out: P4 (waited 4, rode 10)
t=18  up to 8
t=19  floor 8 - out: P8 (waited 2, rode 14)
t=20  up to 9
t=21  floor 9 - out: P2 (waited 0, rode 18), P1 (waited 8, rode 10), P17 (waited 8, rode 10), P18 (waited 12, rode 6); in: P3 (to 0), P13 (to 8)
t=22  down to 8
t=23  floor 8 - out: P13 (waited 18, rode 2); in: P9 (to 1), P20 (to 2)
t=24  down to 7
t=25  floor 7 - in: P5 (to 2), P15 (to 6)
t=26  down to 6
t=27  floor 6 - out: P15 (waited 22, rode 2); in: P7 (to 5)
t=28  down to 5
t=29  floor 5 - out: P7 (waited 24, rode 2); in: P16 (to 0)
t=30  down to 4
t=31  floor 4 - in: P11 (to 3)
t=32  down to 3
t=33  floor 3 - out: P11 (waited 28, rode 2); in: P19 (to 0)
t=34  down to 2
t=35  floor 2 - out: P20 (waited 20, rode 12), P5 (waited 22, rode 10)
t=36  down to 1
t=37  floor 1 - out: P9 (waited 20, rode 14)
t=38  down to 0
t=39  floor 0 - out: P3 (waited 18, rode 18), P16 (waited 26, rode 10), P19 (waited 30, rode 6)
Everyone has arrived; the lift is free at floor 0.

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=40 choice> 5
Calls: 20. Arrived 20, riding 0, waiting 0.
Wait for the lift: average 14.1, longest 30 (P19), over the 20 picked up.
Call to arrival: average 21.8, longest 36 (P3), over the 20 arrived.

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=40 choice> 6
The 20 calls again from t=0 (trip = from the call to arrival):
                  average wait  longest wait  average trip  last arrival
  direction rule          14.1            30          21.8          t=39
  one at a time           88.2           171          93.0         t=181

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=40 choice> 7
Bye.
What was typed (87 lines)
0
x
5
6
3
2
0
21
abc
3
1
10
x

1
4
4

1
4
4
9
1
0
9
1
9
0
1
2
7
1
7
2
1
5
6
1
6
5
1
1
8
1
8
1
1
3
4
1
4
3
1
0
1
1
9
8
1
2
3
1
7
6
1
5
0
1
4
9
1
6
9
1
3
0
1
8
2
1
4
2

3
5
6
7

basic

interpreter: byte-equal
== Elevator ==
One lift, floors 0 to 9. A step of time is one floor of travel or one stop
with the doors open; finish runs on until everyone has arrived.

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=0 choice> 1
from floor> 0
to floor> 8
P1 waits at floor 0 to go up to 8 (t=0).

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=0 choice> 1
from floor> 3
to floor> 6
P2 waits at floor 3 to go up to 6 (t=0).

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=0 choice> 1
from floor> 5
to floor> 9
P3 waits at floor 5 to go up to 9 (t=0).

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=0 choice> 1
from floor> 7
to floor> 2
P4 waits at floor 7 to go down to 2 (t=0).

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=0 choice> 2
how many steps (1 to 20)> 4
t=0  floor 0 - in: P1 (to 8)
t=1-3  up to 3

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=4 choice> 1
from floor> 1
to floor> 4
P5 waits at floor 1 to go up to 4 (t=4).

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=4 choice> 4
 9 |    |
 8 |    |
 7 |    | P4 -> 2
 6 |    |
 5 |    | P3 -> 9
 4 |    |
 3 |[ 1]| P2 -> 6
 2 |    |
 1 |    | P5 -> 4
 0 |    |
t=4: the lift is at floor 3, going up, with P1 -> 8.

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=4 choice> 3
t=4  floor 3 - in: P2 (to 6)
t=5-6  up to 5
t=7  floor 5 - in: P3 (to 9)
t=8  up to 6
t=9  floor 6 - out: P2 (waited 4, rode 5)
t=10-11  up to 8
t=12  floor 8 - out: P1 (waited 0, rode 12)
t=13  up to 9
t=14  floor 9 - out: P3 (waited 7, rode 7)
t=15-16  down to 7
t=17  floor 7 - in: P4 (to 2)
t=18-22  down to 2
t=23  floor 2 - out: P4 (waited 17, rode 6)
t=24  down to 1
t=25  floor 1 - in: P5 (to 4)
t=26-28  up to 4
t=29  floor 4 - out: P5 (waited 21, rode 4)
Everyone has arrived; the lift is free at floor 4.

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=30 choice> 5
Calls: 5. Arrived 5, riding 0, waiting 0.
Wait for the lift: average 9.8, longest 21 (P5), over the 5 picked up.
Call to arrival: average 16.6, longest 25 (P5), over the 5 arrived.

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=30 choice> 6
The 5 calls again from t=0 (trip = from the call to arrival):
                  average wait  longest wait  average trip  last arrival
  direction rule           9.8            21          16.6          t=29
  one at a time           19.6            33          25.2          t=41

1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit
t=30 choice> 7
Bye.
What was typed (22 lines)
1
0
8
1
3
6
1
5
9
1
7
2
2
4
1
1
4
4
3
5
6
7

Modules

The program as written, entry module first. Each module transpiles to its own Python file, which is what eml project run executes.

main.eml(entry)

eml
# P033 elevator: one lift, floors 0 to 9. People call it from a floor to go
# to another, time moves on in steps, and the lift follows the usual rule for
# a single car (collective control). The statistics show how long people
# waited, and the same calls can be run again by a lift that takes one
# passenger at a time, in call order, to compare.
import lift
import report

20 => most_calls
20 => most_steps

def trim(s):
    0 => i
    len(s) => j
    while i < j and s[i] == " ":
        i + 1 => i
    while j > i and s[j - 1] == " ":
        j - 1 => j
    return s[i:j]

def whole_number(s):
    # The value of s if it is 1 to 3 digits, otherwise -1.
    trim(s) => s
    if s == "" or len(s) > 3:
        return -1
    0 => n
    for c in s:
        if not (c in "0123456789"):
            return -1
        n * 10 + int(c) => n
    return n

def ask_floor(prompt):
    # A floor, or -1 when the answer is empty.
    while True:
        trim(input(prompt)) => answer
        if answer == "":
            return -1
        whole_number(answer) => f
        if f >= lift.lowest and f <= lift.highest:
            return f
        ("Type a floor from " + str(lift.lowest) + " to " + str(lift.highest) + ".") ^0

def ask_destination(start):
    # A floor other than start, or -1 when the answer is empty.
    while True:
        ask_floor("to floor> ") => f
        if f != start:
            return f
        "That is the floor they are on." ^0

def call(state, calls):
    # Returns [state, calls] with the new caller waiting.
    if len(calls) == most_calls:
        ("That makes " + str(most_calls) + " calls, the most for one run.") ^0
        return [state, calls]
    ask_floor("from floor> ") => a
    if a == -1:
        "Cancelled." ^0
        return [state, calls]
    ask_destination(a) => b
    if b == -1:
        "Cancelled." ^0
        return [state, calls]
    len(calls) + 1 => n
    calls + [[n, a, b, state[0]]] => calls
    state[3] + [[n, a, b, state[0], -1, -1]] => waiting
    "up" => way
    if b < a:
        "down" => way
    ("P" + str(n) + " waits at floor " + str(a) + " to go " + way + " to " + str(b) + " (t=" + str(state[0]) + ").") ^0
    return [[state[0], state[1], state[2], waiting, state[4], state[5]], calls]

def advance(state, n):
    [] => events
    for k in [1:n]:
        lift.step(state, False) => r
        r[0] => state
        events + [r[1]] => events
    for line in report.event_lines(events):
        line ^0
    return state

def steps(state):
    while True:
        trim(input("how many steps (1 to " + str(most_steps) + ")> ")) => answer
        if answer == "":
            "Cancelled." ^0
            return state
        whole_number(answer) => n
        if n >= 1 and n <= most_steps:
            return advance(state, n)
        ("Type a number from 1 to " + str(most_steps) + ".") ^0

def run_to_end(state):
    if not lift.busy(state):
        "Nobody is waiting or riding." ^0
        return state
    [] => events
    while lift.busy(state):
        lift.step(state, False) => r
        r[0] => state
        events + [r[1]] => events
    for line in report.event_lines(events):
        line ^0
    ("Everyone has arrived; the lift is free at floor " + str(state[1]) + ".") ^0
    return state

"== Elevator ==" ^0
"One lift, floors 0 to 9. A step of time is one floor of travel or one stop" ^0
"with the doors open; finish runs on until everyone has arrived." ^0
[0, lift.lowest, 0, [], [], []] => state
[] => calls
True => running
while running:
    "" ^0
    "1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit" ^0
    trim(input("t=" + str(state[0]) + " choice> ")) => choice
    if choice == "1":
        call(state, calls) => r
        r[0] => state
        r[1] => calls
    elif choice == "2":
        steps(state) => state
    elif choice == "3":
        run_to_end(state) => state
    elif choice == "4":
        report.building(state)
    elif choice == "5":
        report.statistics(state)
    elif choice == "6":
        report.compare(calls)
    elif choice == "7":
        False => running
    else:
        "Pick a number from 1 to 7." ^0
"Bye." ^0
Python projection (main.py)
import lift
import report
most_calls = 20
most_steps = 20

def trim(s):
    i = 0
    j = len(s)
    while i < j and s[i] == " ":
        i = i + 1
    while j > i and s[j - 1] == " ":
        j = j - 1
    return s[i:j]

def whole_number(s):
    s = trim(s)
    if s == "" or len(s) > 3:
        return -1
    n = 0
    for c in s:
        if not c in "0123456789":
            return -1
        n = n * 10 + int(c)
    return n

def ask_floor(prompt):
    while True:
        answer = trim(input(prompt))
        if answer == "":
            return -1
        f = whole_number(answer)
        if f >= lift.lowest and f <= lift.highest:
            return f
        print("Type a floor from " + str(lift.lowest) + " to " + str(lift.highest) + ".")

def ask_destination(start):
    while True:
        f = ask_floor("to floor> ")
        if f != start:
            return f
        print("That is the floor they are on.")

def call(state, calls):
    if len(calls) == most_calls:
        print("That makes " + str(most_calls) + " calls, the most for one run.")
        return [state, calls]
    a = ask_floor("from floor> ")
    if a == -1:
        print("Cancelled.")
        return [state, calls]
    b = ask_destination(a)
    if b == -1:
        print("Cancelled.")
        return [state, calls]
    n = len(calls) + 1
    calls = calls + [[n, a, b, state[0]]]
    waiting = state[3] + [[n, a, b, state[0], -1, -1]]
    way = "up"
    if b < a:
        way = "down"
    print("P" + str(n) + " waits at floor " + str(a) + " to go " + way + " to " + str(b) + " (t=" + str(state[0]) + ").")
    return [[state[0], state[1], state[2], waiting, state[4], state[5]], calls]

def advance(state, n):
    events = []
    for k in range(1, n+1):
        r = lift.step(state, False)
        state = r[0]
        events = events + [r[1]]
    for line in report.event_lines(events):
        print(line)
    return state

def steps(state):
    while True:
        answer = trim(input("how many steps (1 to " + str(most_steps) + ")> "))
        if answer == "":
            print("Cancelled.")
            return state
        n = whole_number(answer)
        if n >= 1 and n <= most_steps:
            return advance(state, n)
        print("Type a number from 1 to " + str(most_steps) + ".")

def run_to_end(state):
    if not lift.busy(state):
        print("Nobody is waiting or riding.")
        return state
    events = []
    while lift.busy(state):
        r = lift.step(state, False)
        state = r[0]
        events = events + [r[1]]
    for line in report.event_lines(events):
        print(line)
    print("Everyone has arrived; the lift is free at floor " + str(state[1]) + ".")
    return state

print("== Elevator ==")
print("One lift, floors 0 to 9. A step of time is one floor of travel or one stop")
print("with the doors open; finish runs on until everyone has arrived.")
state = [0, lift.lowest, 0, [], [], []]
calls = []
running = True
while running:
    print("")
    print("1) call  2) steps  3) finish  4) building  5) statistics  6) compare  7) quit")
    choice = trim(input("t=" + str(state[0]) + " choice> "))
    if choice == "1":
        r = call(state, calls)
        state = r[0]
        calls = r[1]
    elif choice == "2":
        state = steps(state)
    elif choice == "3":
        state = run_to_end(state)
    elif choice == "4":
        report.building(state)
    elif choice == "5":
        report.statistics(state)
    elif choice == "6":
        report.compare(calls)
    elif choice == "7":
        running = False
    else:
        print("Pick a number from 1 to 7.")
print("Bye.")

lift.eml

eml
# P033 elevator - the lift and its rules. Floors 0 to 9. One step of time is
# one floor of travel, or one stop with the doors open.
#
# A passenger is [number, from, to, call time, boarding time, arrival time];
# the last two are -1 until they happen. The state of a run is
# [time, floor, direction, waiting, riding, arrived], where the direction is
# 1 going up, -1 going down and 0 idle, and waiting is kept in call order.

0 => lowest
9 => highest

def sign(x):
    if x > 0:
        return 1
    if x < 0:
        return -1
    return 0

def way(p):
    # 1 for a passenger who wants to go up, -1 for one going down.
    return sign(p[2] - p[1])

def reason(floor, d, waiting, riding):
    # Is there a reason to go on in direction d from this floor: a rider going
    # further that way, a call from a floor further that way, or someone
    # waiting right here who wants to go that way?
    for p in riding:
        if sign(p[2] - floor) == d:
            return True
    for p in waiting:
        if sign(p[1] - floor) == d:
            return True
        if p[1] == floor and way(p) == d:
            return True
    return False

def step(state, one_at_a_time):
    # One step of time. Returns [the new state, what happened], where what
    # happened is [kind, time, floor, direction, got out, got in] and kind is
    # "doors", "move" or "idle".
    state[0] => t
    state[1] => floor
    state[2] => d
    state[3] => waiting
    state[4] => riding
    [] => out
    [] => went_in
    if one_at_a_time:
        # One passenger at a time, in call order: fetch the first caller,
        # take them where they are going, then the next.
        if len(riding) > 0 and riding[0][2] == floor:
            riding[0] => p
            out + [[p[0], p[1], p[2], p[3], p[4], t]] => out
            [] => riding
        if len(riding) == 0 and len(waiting) > 0 and waiting[0][1] == floor:
            waiting[0] => p
            [[p[0], p[1], p[2], p[3], t, -1]] => went_in
            went_in => riding
            waiting[1:len(waiting)] => waiting
        0 => d
        if len(riding) > 0:
            sign(riding[0][2] - floor) => d
        elif len(waiting) > 0:
            sign(waiting[0][1] - floor) => d
    else:
        # The direction rule: riders get out at their floor; the lift keeps
        # its direction while there is a reason to go on, turns round when
        # there is none that way but some the other way, and otherwise goes
        # idle. An idle lift heads for the earliest call. Whoever waits here
        # and wants to go the lift's way gets in.
        [] => kept
        for p in riding:
            if p[2] == floor:
                out + [[p[0], p[1], p[2], p[3], p[4], t]] => out
            else:
                kept + [p] => kept
        kept => riding
        if d != 0 and not reason(floor, d, waiting, riding):
            if reason(floor, 0 - d, waiting, riding):
                0 - d => d
            else:
                0 => d
        if d == 0 and len(waiting) > 0:
            waiting[0] => first
            if first[1] == floor:
                way(first) => d
            else:
                sign(first[1] - floor) => d
        [] => left
        for p in waiting:
            if p[1] == floor and way(p) == d:
                went_in + [[p[0], p[1], p[2], p[3], t, -1]] => went_in
            else:
                left + [p] => left
        left => waiting
        riding + went_in => riding
    if len(out) > 0 or len(went_in) > 0:
        "doors" => kind
    elif d != 0:
        floor + d => floor
        "move" => kind
    else:
        "idle" => kind
    return [[t + 1, floor, d, waiting, riding, state[5] + out], [kind, t, floor, d, out, went_in]]

def busy(state):
    return len(state[3]) > 0 or len(state[4]) > 0

def run_all(calls, one_at_a_time):
    # Runs the calls [number, from, to, call time] (in call order) from time 0
    # with an idle lift at the ground floor, until everyone has arrived.
    # Returns the final state.
    [0, lowest, 0, [], [], []] => state
    0 => k
    while k < len(calls) or busy(state):
        while k < len(calls) and calls[k][3] == state[0]:
            calls[k] => c
            [state[0], state[1], state[2], state[3] + [[c[0], c[1], c[2], c[3], -1, -1]], state[4], state[5]] => state
            k + 1 => k
        step(state, one_at_a_time)[0] => state
    return state
Python projection (lift.py)
lowest = 0
highest = 9

def sign(x):
    if x > 0:
        return 1
    if x < 0:
        return -1
    return 0

def way(p):
    return sign(p[2] - p[1])

def reason(floor, d, waiting, riding):
    for p in riding:
        if sign(p[2] - floor) == d:
            return True
    for p in waiting:
        if sign(p[1] - floor) == d:
            return True
        if p[1] == floor and way(p) == d:
            return True
    return False

def step(state, one_at_a_time):
    t = state[0]
    floor = state[1]
    d = state[2]
    waiting = state[3]
    riding = state[4]
    out = []
    went_in = []
    if one_at_a_time:
        if len(riding) > 0 and riding[0][2] == floor:
            p = riding[0]
            out = out + [[p[0], p[1], p[2], p[3], p[4], t]]
            riding = []
        if len(riding) == 0 and len(waiting) > 0 and waiting[0][1] == floor:
            p = waiting[0]
            went_in = [[p[0], p[1], p[2], p[3], t, -1]]
            riding = went_in
            waiting = waiting[1:len(waiting)]
        d = 0
        if len(riding) > 0:
            d = sign(riding[0][2] - floor)
        elif len(waiting) > 0:
            d = sign(waiting[0][1] - floor)
    else:
        kept = []
        for p in riding:
            if p[2] == floor:
                out = out + [[p[0], p[1], p[2], p[3], p[4], t]]
            else:
                kept = kept + [p]
        riding = kept
        if d != 0 and not reason(floor, d, waiting, riding):
            if reason(floor, 0 - d, waiting, riding):
                d = 0 - d
            else:
                d = 0
        if d == 0 and len(waiting) > 0:
            first = waiting[0]
            if first[1] == floor:
                d = way(first)
            else:
                d = sign(first[1] - floor)
        left = []
        for p in waiting:
            if p[1] == floor and way(p) == d:
                went_in = went_in + [[p[0], p[1], p[2], p[3], t, -1]]
            else:
                left = left + [p]
        waiting = left
        riding = riding + went_in
    if len(out) > 0 or len(went_in) > 0:
        kind = "doors"
    elif d != 0:
        floor = floor + d
        kind = "move"
    else:
        kind = "idle"
    return [[t + 1, floor, d, waiting, riding, state[5] + out], [kind, t, floor, d, out, went_in]]

def busy(state):
    return len(state[3]) > 0 or len(state[4]) > 0

def run_all(calls, one_at_a_time):
    state = [0, lowest, 0, [], [], []]
    k = 0
    while k < len(calls) or busy(state):
        while k < len(calls) and calls[k][3] == state[0]:
            c = calls[k]
            state = [state[0], state[1], state[2], state[3] + [[c[0], c[1], c[2], c[3], -1, -1]], state[4], state[5]]
            k = k + 1
        state = step(state, one_at_a_time)[0]
    return state

report.eml

eml
# P033 elevator - what the screen shows: the steps, the building, the
# statistics and the comparison. Times are whole steps; averages are worked
# out in whole numbers and shown to one decimal place, halves rounded up.
import lift

def quotient(a, b):
    return int((a - a % b) / b)

def average(total, n):
    # total / n to one decimal place: 11 / 5 is "2.2", 9 / 4 is "2.3".
    quotient(total * 20 + n, n * 2) => tenths
    return str(quotient(tenths, 10)) + "." + str(tenths % 10)

def going(d):
    if d == 1:
        return "going up"
    if d == -1:
        return "going down"
    return "idle"

def who(p):
    return "P" + str(p[0])

def doors_line(e):
    ("t=" + str(e[1]) + "  floor " + str(e[2]) + " -") => line
    if len(e[4]) > 0:
        "" => part
        for p in e[4]:
            if part != "":
                part + ", " => part
            part + who(p) + " (waited " + str(p[4] - p[3]) + ", rode " + str(p[5] - p[4]) + ")" => part
        line + " out: " + part => line
        if len(e[5]) > 0:
            line + ";" => line
    if len(e[5]) > 0:
        "" => part
        for p in e[5]:
            if part != "":
                part + ", " => part
            part + who(p) + " (to " + str(p[2]) + ")" => part
        line + " in: " + part => line
    return line

def event_lines(events):
    # One line for each stop; a run of moves the same way, or of idle steps,
    # becomes one line: "t=3-6  up to 7".
    [] => lines
    0 => i
    while i < len(events):
        events[i] => e
        if e[0] == "doors":
            lines + [doors_line(e)] => lines
            i + 1 => i
        else:
            i => j
            while j + 1 < len(events) and events[j + 1][0] == e[0] and events[j + 1][3] == e[3]:
                j + 1 => j
            "t=" + str(e[1]) => when
            if j > i:
                when + "-" + str(events[j][1]) => when
            if e[0] == "idle":
                lines + [when + "  idle at floor " + str(e[2])] => lines
            elif e[3] == 1:
                lines + [when + "  up to " + str(events[j][2])] => lines
            else:
                lines + [when + "  down to " + str(events[j][2])] => lines
            j + 1 => i
    return lines

def trip(p):
    return who(p) + " -> " + str(p[2])

def building(state):
    lift.highest => f
    while f >= lift.lowest:
        "    " => car
        if state[1] == f:
            str(len(state[4])) => n
            if len(n) < 2:
                " " + n => n
            "[" + n + "]" => car
        "" => calls
        for p in state[3]:
            if p[1] == f:
                if calls != "":
                    calls + ", " => calls
                calls + trip(p) => calls
        (" " + str(f) + " |" + car + "|") => line
        if calls != "":
            line + " " + calls => line
        line ^0
        f - 1 => f
    ("t=" + str(state[0]) + ": the lift is at floor " + str(state[1]) + ", " + going(state[2])) => line
    if len(state[4]) == 0:
        line + ", empty." => line
    else:
        "" => riders
        for p in state[4]:
            if riders != "":
                riders + ", " => riders
            riders + trip(p) => riders
        line + ", with " + riders + "." => line
    line ^0

def longest(ps, k, j):
    # The passenger with the longest time from field k to field j: the
    # lowest number on a tie. Returns [time, passenger].
    -1 => best
    [] => whose
    for p in ps:
        p[j] - p[k] => v
        if v > best or (v == best and p[0] < whose[0]):
            v => best
            p => whose
    return [best, whose]

def total(ps, k, j):
    0 => s
    for p in ps:
        s + p[j] - p[k] => s
    return s

def statistics(state):
    state[5] => arrived
    arrived + state[4] => picked
    len(arrived) + len(state[4]) + len(state[3]) => calls
    if calls == 0:
        "Nobody has called the lift yet." ^0
        return 0
    ("Calls: " + str(calls) + ". Arrived " + str(len(arrived)) + ", riding " + str(len(state[4])) + ", waiting " + str(len(state[3])) + ".") ^0
    if len(picked) == 0:
        "Nobody has been picked up yet." ^0
    else:
        longest(picked, 3, 4) => w
        ("Wait for the lift: average " + average(total(picked, 3, 4), len(picked)) + ", longest " + str(w[0]) + " (" + who(w[1]) + "), over the " + str(len(picked)) + " picked up.") ^0
    if len(arrived) > 0:
        longest(arrived, 3, 5) => a
        ("Call to arrival: average " + average(total(arrived, 3, 5), len(arrived)) + ", longest " + str(a[0]) + " (" + who(a[1]) + "), over the " + str(len(arrived)) + " arrived.") ^0
    for p in state[3]:
        (who(p) + " has waited " + str(state[0] - p[3]) + " so far at floor " + str(p[1]) + ".") ^0
    return 0

def right(s, width):
    while len(s) < width:
        " " + s => s
    return s

def row(label, final):
    final[5] => ps
    0 => last
    for p in ps:
        if p[5] > last:
            p[5] => last
    (label + right(average(total(ps, 3, 4), len(ps)), 14) + right(str(longest(ps, 3, 4)[0]), 14) + right(average(total(ps, 3, 5), len(ps)), 14) + right("t=" + str(last), 14)) ^0

def compare(calls):
    # The same calls at the same times, run from t=0 by each kind of lift.
    if len(calls) == 0:
        "Nobody has called the lift yet." ^0
        return 0
    "call" => what
    if len(calls) > 1:
        "calls" => what
    ("The " + str(len(calls)) + " " + what + " again from t=0 (trip = from the call to arrival):") ^0
    "                  average wait  longest wait  average trip  last arrival" ^0
    row("  direction rule", lift.run_all(calls, False))
    row("  one at a time ", lift.run_all(calls, True))
    return 0
Python projection (report.py)
import lift

def quotient(a, b):
    return int((a - a % b) / b)

def average(total, n):
    tenths = quotient(total * 20 + n, n * 2)
    return str(quotient(tenths, 10)) + "." + str(tenths % 10)

def going(d):
    if d == 1:
        return "going up"
    if d == -1:
        return "going down"
    return "idle"

def who(p):
    return "P" + str(p[0])

def doors_line(e):
    line = "t=" + str(e[1]) + "  floor " + str(e[2]) + " -"
    if len(e[4]) > 0:
        part = ""
        for p in e[4]:
            if part != "":
                part = part + ", "
            part = part + who(p) + " (waited " + str(p[4] - p[3]) + ", rode " + str(p[5] - p[4]) + ")"
        line = line + " out: " + part
        if len(e[5]) > 0:
            line = line + ";"
    if len(e[5]) > 0:
        part = ""
        for p in e[5]:
            if part != "":
                part = part + ", "
            part = part + who(p) + " (to " + str(p[2]) + ")"
        line = line + " in: " + part
    return line

def event_lines(events):
    lines = []
    i = 0
    while i < len(events):
        e = events[i]
        if e[0] == "doors":
            lines = lines + [doors_line(e)]
            i = i + 1
        else:
            j = i
            while j + 1 < len(events) and events[j + 1][0] == e[0] and events[j + 1][3] == e[3]:
                j = j + 1
            when = "t=" + str(e[1])
            if j > i:
                when = when + "-" + str(events[j][1])
            if e[0] == "idle":
                lines = lines + [when + "  idle at floor " + str(e[2])]
            elif e[3] == 1:
                lines = lines + [when + "  up to " + str(events[j][2])]
            else:
                lines = lines + [when + "  down to " + str(events[j][2])]
            i = j + 1
    return lines

def trip(p):
    return who(p) + " -> " + str(p[2])

def building(state):
    f = lift.highest
    while f >= lift.lowest:
        car = "    "
        if state[1] == f:
            n = str(len(state[4]))
            if len(n) < 2:
                n = " " + n
            car = "[" + n + "]"
        calls = ""
        for p in state[3]:
            if p[1] == f:
                if calls != "":
                    calls = calls + ", "
                calls = calls + trip(p)
        line = " " + str(f) + " |" + car + "|"
        if calls != "":
            line = line + " " + calls
        print(line)
        f = f - 1
    line = "t=" + str(state[0]) + ": the lift is at floor " + str(state[1]) + ", " + going(state[2])
    if len(state[4]) == 0:
        line = line + ", empty."
    else:
        riders = ""
        for p in state[4]:
            if riders != "":
                riders = riders + ", "
            riders = riders + trip(p)
        line = line + ", with " + riders + "."
    print(line)

def longest(ps, k, j):
    best = -1
    whose = []
    for p in ps:
        v = p[j] - p[k]
        if v > best or v == best and p[0] < whose[0]:
            best = v
            whose = p
    return [best, whose]

def total(ps, k, j):
    s = 0
    for p in ps:
        s = s + p[j] - p[k]
    return s

def statistics(state):
    arrived = state[5]
    picked = arrived + state[4]
    calls = len(arrived) + len(state[4]) + len(state[3])
    if calls == 0:
        print("Nobody has called the lift yet.")
        return 0
    print("Calls: " + str(calls) + ". Arrived " + str(len(arrived)) + ", riding " + str(len(state[4])) + ", waiting " + str(len(state[3])) + ".")
    if len(picked) == 0:
        print("Nobody has been picked up yet.")
    else:
        w = longest(picked, 3, 4)
        print("Wait for the lift: average " + average(total(picked, 3, 4), len(picked)) + ", longest " + str(w[0]) + " (" + who(w[1]) + "), over the " + str(len(picked)) + " picked up.")
    if len(arrived) > 0:
        a = longest(arrived, 3, 5)
        print("Call to arrival: average " + average(total(arrived, 3, 5), len(arrived)) + ", longest " + str(a[0]) + " (" + who(a[1]) + "), over the " + str(len(arrived)) + " arrived.")
    for p in state[3]:
        print(who(p) + " has waited " + str(state[0] - p[3]) + " so far at floor " + str(p[1]) + ".")
    return 0

def right(s, width):
    while len(s) < width:
        s = " " + s
    return s

def row(label, final):
    ps = final[5]
    last = 0
    for p in ps:
        if p[5] > last:
            last = p[5]
    print(label + right(average(total(ps, 3, 4), len(ps)), 14) + right(str(longest(ps, 3, 4)[0]), 14) + right(average(total(ps, 3, 5), len(ps)), 14) + right("t=" + str(last), 14))

def compare(calls):
    if len(calls) == 0:
        print("Nobody has called the lift yet.")
        return 0
    what = "call"
    if len(calls) > 1:
        what = "calls"
    print("The " + str(len(calls)) + " " + what + " again from t=0 (trip = from the call to arrival):")
    print("                  average wait  longest wait  average trip  last arrival")
    row("  direction rule", lift.run_all(calls, False))
    row("  one at a time ", lift.run_all(calls, True))
    return 0

Built on these corpus cases