<!-- canonical: https://efficientnewlanguage.org/eml-p/projects/P053-help-desk/ | updated: 2026-10-10 -->

# P053 Help desk queue

Support tickets with four priorities, served most urgent first and first come, first served within a priority, kept in a binary heap. Open, serve, list the line, change a waiting ticket's priority or cancel it, and see the queue grouped by priority next to what has been served.

EML-P project `projects/help-desk` in the EML language repo: 2 module(s), entry `main.eml`, terminal UI. There, `eml project run projects/help-desk` runs it and `eml project verify projects/help-desk` replays every session under CPython (two hash seeds) and in the interpreter; the site build replays every session in the interpreter again and publishes a session only if its screen matches.

Built on verified corpus cases: binary-heap (https://efficientnewlanguage.org/cases/096-binary-heap/), task-priority-bucketer (https://efficientnewlanguage.org/cases/045-task-priority-bucketer/).

## Sessions

### bad-input - interpreter: byte-equal to the golden

Input:

```text
0
x
2
3
4
5
6
1

1
This title is far too long to be accepted here
Short title
5
x

1
Broken chair
4
4
x
#99
1
4
4
#1

5

7
7
7
7
1
Extra one
3
1
Extra two
3
1
Extra three
3
1
Extra four
3
1
Extra five
3
1
6
8
```

Screen:

```text
== Help desk ==
Tickets are served most urgent first, and in the order they came within a priority.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 0
Pick a number from 1 to 8.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> x
Pick a number from 1 to 8.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 2
No ticket is waiting.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 3
No ticket is waiting.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 4
No ticket is waiting.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 5
No ticket is waiting.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 6
priority  waiting  served
urgent          0       0
high            0       0
normal          0       0
low             0       0
Waiting 0, served 0, next ticket number #1.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 1
title> 
Cancelled.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 1
title> This title is far too long to be accepted here
Keep the title to 40 characters.
title> Short title
priority (1 urgent, 2 high, 3 normal, 4 low)> 5
Type 1, 2, 3 or 4.
priority (1 urgent, 2 high, 3 normal, 4 low)> x
Type 1, 2, 3 or 4.
priority (1 urgent, 2 high, 3 normal, 4 low)> 
Cancelled.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 1
title> Broken chair
priority (1 urgent, 2 high, 3 normal, 4 low)> 4
Opened #1 Broken chair (low): number 1 in line of 1.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 4
ticket> x
Type a ticket number, like 3 or #3.
ticket> #99
No ticket #99 is waiting.
ticket> 1
new priority (1 urgent, 2 high, 3 normal, 4 low)> 4
#1 is low already.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 4
ticket> #1
new priority (1 urgent, 2 high, 3 normal, 4 low)> 
Cancelled.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 5
ticket to cancel> 
Cancelled.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 7
Opened 8 sample tickets, #2 to #9.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 7
Opened 8 sample tickets, #10 to #17.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 7
Opened 8 sample tickets, #18 to #25.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 7
The sample would take the queue past 30 tickets.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 1
title> Extra one
priority (1 urgent, 2 high, 3 normal, 4 low)> 3
Opened #26 Extra one (normal): number 19 in line of 26.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 1
title> Extra two
priority (1 urgent, 2 high, 3 normal, 4 low)> 3
Opened #27 Extra two (normal): number 20 in line of 27.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 1
title> Extra three
priority (1 urgent, 2 high, 3 normal, 4 low)> 3
Opened #28 Extra three (normal): number 21 in line of 28.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 1
title> Extra four
priority (1 urgent, 2 high, 3 normal, 4 low)> 3
Opened #29 Extra four (normal): number 22 in line of 29.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 1
title> Extra five
priority (1 urgent, 2 high, 3 normal, 4 low)> 3
Opened #30 Extra five (normal): number 23 in line of 30.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 1
30 tickets are waiting, the most the queue holds - serve some first.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 6
priority  waiting  served
urgent          6       0  ######
high            6       0  ######
normal         11       0  ###########
low             7       0  #######
Waiting 30, served 0, next ticket number #31.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 8
Bye.
```

### basic - interpreter: byte-equal to the golden

Input:

```text
7
3
2
2
4
8
1
5
#6
1
Coffee machine leaks
2
6
2
2
3
8
```

Screen:

```text
== Help desk ==
Tickets are served most urgent first, and in the order they came within a priority.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 7
Opened 8 sample tickets, #1 to #8.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 3
  1. #3 Server room too warm (urgent)
  2. #7 Payroll system down (urgent)
  3. #2 Cannot log in to email (high)
  4. #5 VPN drops every hour (high)
  5. #4 New laptop for Maria (normal)
  6. #8 Monitor flickers (normal)
  7. #1 Printer out of toner (low)
  8. #6 Update the wiki page (low)

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 2
Serving #3 Server room too warm (urgent). 7 tickets still waiting.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 2
Serving #7 Payroll system down (urgent). 6 tickets still waiting.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 4
ticket> 8
new priority (1 urgent, 2 high, 3 normal, 4 low)> 1
#8 Monitor flickers: normal -> urgent, now number 1 in line.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 5
ticket to cancel> #6
Cancelled #6 Update the wiki page (low). 5 tickets still waiting.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 1
title> Coffee machine leaks
priority (1 urgent, 2 high, 3 normal, 4 low)> 2
Opened #9 Coffee machine leaks (high): number 4 in line of 6.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 6
priority  waiting  served
urgent          1       2  #
high            3       0  ###
normal          1       0  #
low             1       0  #
Waiting 6, served 2, next ticket number #10.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 2
Serving #8 Monitor flickers (urgent). 5 tickets still waiting.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 2
Serving #2 Cannot log in to email (high). 4 tickets still waiting.

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 3
  1. #5 VPN drops every hour (high)
  2. #9 Coffee machine leaks (high)
  3. #4 New laptop for Maria (normal)
  4. #1 Printer out of toner (low)

1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit
choice> 8
Bye.
```

## Modules

### main.eml

```eml
# P053 help-desk queue: open support tickets with a priority, serve them
# most urgent first and, within a priority, in the order they came in;
# change a waiting ticket's priority or cancel it, and see the queue grouped
# by priority.
import heap

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 level_name(p):
    return ["urgent", "high", "normal", "low"][p - 1]

def counted(n, one, many):
    if n == 1:
        return "1 " + one
    return str(n) + " " + many

def ticket(t):
    return "#" + str(t[1]) + " " + t[2] + " (" + level_name(t[0]) + ")"

def place_in_line(q, number):
    heap.service_order(q[0]) => order
    for k in [0:len(order) - 1]:
        if order[k][1] == number:
            return k + 1
    return 0

def ask_priority(prompt):
    while True:
        trim(input(prompt + " (1 urgent, 2 high, 3 normal, 4 low)> ")) => answer
        if answer == "":
            return 0
        if answer == "1" or answer == "2" or answer == "3" or answer == "4":
            return int(answer)
        "Type 1, 2, 3 or 4." ^0

def ask_ticket(q, prompt):
    # The index in the heap of a waiting ticket, or -1 when the answer is
    # empty.
    while True:
        trim(input(prompt)) => answer
        if answer == "":
            return -1
        if len(answer) > 1 and answer[0] == "#":
            answer[1:len(answer)] => answer
        True => digits
        for c in answer:
            if not (c >= "0" and c <= "9"):
                False => digits
        if answer != "" and digits and len(answer) < 6:
            heap.find(q[0], int(answer)) => i
            if i >= 0:
                return i
            ("No ticket #" + answer + " is waiting.") ^0
        else:
            "Type a ticket number, like 3 or #3." ^0

def add(q, title, priority):
    [priority, q[1], title] => t
    q[1] + 1 => q[1]
    heap.push(q[0], t) => q[0]
    return t

def new_ticket(q):
    if len(q[0]) == 30:
        "30 tickets are waiting, the most the queue holds - serve some first." ^0
        return
    while True:
        trim(input("title> ")) => title
        if title == "":
            "Cancelled." ^0
            return
        if len(title) <= 40:
            break
        "Keep the title to 40 characters." ^0
    ask_priority("priority") => p
    if p == 0:
        "Cancelled." ^0
        return
    add(q, title, p) => t
    ("Opened " + ticket(t) + ": number " + str(place_in_line(q, t[1])) + " in line of " + str(len(q[0])) + ".") ^0

def serve(q):
    if len(q[0]) == 0:
        "No ticket is waiting." ^0
        return
    heap.pop(q[0]) => res
    res[1] => q[0]
    res[0] => t
    q[2][t[0] - 1] + 1 => q[2][t[0] - 1]
    ("Serving " + ticket(t) + ". " + counted(len(q[0]), "ticket", "tickets") + " still waiting.") ^0

def waiting(q):
    if len(q[0]) == 0:
        "No ticket is waiting." ^0
        return
    heap.service_order(q[0]) => order
    for k in [0:len(order) - 1]:
        str(k + 1) => n
        while len(n) < 3:
            " " + n => n
        (n + ". " + ticket(order[k])) ^0

def change(q):
    if len(q[0]) == 0:
        "No ticket is waiting." ^0
        return
    ask_ticket(q, "ticket> ") => i
    if i == -1:
        "Cancelled." ^0
        return
    q[0][i] => t
    ask_priority("new priority") => p
    if p == 0:
        "Cancelled." ^0
        return
    if p == t[0]:
        ("#" + str(t[1]) + " is " + level_name(p) + " already.") ^0
        return
    level_name(t[0]) => was
    heap.reprioritized(q[0], i, p)
    ("#" + str(t[1]) + " " + t[2] + ": " + was + " -> " + level_name(p) + ", now number " + str(place_in_line(q, t[1])) + " in line.") ^0

def cancel(q):
    if len(q[0]) == 0:
        "No ticket is waiting." ^0
        return
    ask_ticket(q, "ticket to cancel> ") => i
    if i == -1:
        "Cancelled." ^0
        return
    heap.removed_at(q[0], i) => res
    res[1] => q[0]
    ("Cancelled " + ticket(res[0]) + ". " + counted(len(q[0]), "ticket", "tickets") + " still waiting.") ^0

def summary(q):
    # Waiting tickets in one bucket per priority, as the corpus case
    # task-priority-bucketer sorts tasks into buckets, and how many of each
    # priority have been served.
    [0, 0, 0, 0] => counts
    for t in q[0]:
        counts[t[0] - 1] + 1 => counts[t[0] - 1]
    "priority  waiting  served" ^0
    for p in [1:4]:
        level_name(p) => name
        while len(name) < 10:
            name + " " => name
        str(counts[p - 1]) => w
        while len(w) < 7:
            " " + w => w
        str(q[2][p - 1]) => s
        while len(s) < 8:
            " " + s => s
        name + w + s => line
        if counts[p - 1] > 0:
            line + "  " + "#" * counts[p - 1] => line
        line ^0
    ("Waiting " + str(len(q[0])) + ", served " + str(q[2][0] + q[2][1] + q[2][2] + q[2][3]) + ", next ticket number #" + str(q[1]) + ".") ^0

def sample(q):
    for t in [["Printer out of toner", 4], ["Cannot log in to email", 2], ["Server room too warm", 1],
              ["New laptop for Maria", 3], ["VPN drops every hour", 2], ["Update the wiki page", 4],
              ["Payroll system down", 1], ["Monitor flickers", 3]]:
        add(q, t[0], t[1])

"== Help desk ==" ^0
"Tickets are served most urgent first, and in the order they came within a priority." ^0
# [the heap, the next ticket number, served per priority]
[[], 1, [0, 0, 0, 0]] => q
True => running
while running:
    "" ^0
    "1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit" ^0
    trim(input("choice> ")) => choice
    if choice == "1":
        new_ticket(q)
    elif choice == "2":
        serve(q)
    elif choice == "3":
        waiting(q)
    elif choice == "4":
        change(q)
    elif choice == "5":
        cancel(q)
    elif choice == "6":
        summary(q)
    elif choice == "7":
        if len(q[0]) + 8 > 30:
            "The sample would take the queue past 30 tickets." ^0
        else:
            q[1] => first
            sample(q)
            ("Opened 8 sample tickets, #" + str(first) + " to #" + str(q[1] - 1) + ".") ^0
    elif choice == "8":
        False => running
    else:
        "Pick a number from 1 to 8." ^0
"Bye." ^0
```

Python projection of main.eml:

```python
import heap

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 level_name(p):
    return ["urgent", "high", "normal", "low"][p - 1]

def counted(n, one, many):
    if n == 1:
        return "1 " + one
    return str(n) + " " + many

def ticket(t):
    return "#" + str(t[1]) + " " + t[2] + " (" + level_name(t[0]) + ")"

def place_in_line(q, number):
    order = heap.service_order(q[0])
    for k in range(0, len(order)):
        if order[k][1] == number:
            return k + 1
    return 0

def ask_priority(prompt):
    while True:
        answer = trim(input(prompt + " (1 urgent, 2 high, 3 normal, 4 low)> "))
        if answer == "":
            return 0
        if answer == "1" or answer == "2" or answer == "3" or answer == "4":
            return int(answer)
        print("Type 1, 2, 3 or 4.")

def ask_ticket(q, prompt):
    while True:
        answer = trim(input(prompt))
        if answer == "":
            return -1
        if len(answer) > 1 and answer[0] == "#":
            answer = answer[1:len(answer)]
        digits = True
        for c in answer:
            if not (c >= "0" and c <= "9"):
                digits = False
        if answer != "" and digits and len(answer) < 6:
            i = heap.find(q[0], int(answer))
            if i >= 0:
                return i
            print("No ticket #" + answer + " is waiting.")
        else:
            print("Type a ticket number, like 3 or #3.")

def add(q, title, priority):
    t = [priority, q[1], title]
    q[1] = q[1] + 1
    q[0] = heap.push(q[0], t)
    return t

def new_ticket(q):
    if len(q[0]) == 30:
        print("30 tickets are waiting, the most the queue holds - serve some first.")
        return
    while True:
        title = trim(input("title> "))
        if title == "":
            print("Cancelled.")
            return
        if len(title) <= 40:
            break
        print("Keep the title to 40 characters.")
    p = ask_priority("priority")
    if p == 0:
        print("Cancelled.")
        return
    t = add(q, title, p)
    print("Opened " + ticket(t) + ": number " + str(place_in_line(q, t[1])) + " in line of " + str(len(q[0])) + ".")

def serve(q):
    if len(q[0]) == 0:
        print("No ticket is waiting.")
        return
    res = heap.pop(q[0])
    q[0] = res[1]
    t = res[0]
    q[2][t[0] - 1] = q[2][t[0] - 1] + 1
    print("Serving " + ticket(t) + ". " + counted(len(q[0]), "ticket", "tickets") + " still waiting.")

def waiting(q):
    if len(q[0]) == 0:
        print("No ticket is waiting.")
        return
    order = heap.service_order(q[0])
    for k in range(0, len(order)):
        n = str(k + 1)
        while len(n) < 3:
            n = " " + n
        print(n + ". " + ticket(order[k]))

def change(q):
    if len(q[0]) == 0:
        print("No ticket is waiting.")
        return
    i = ask_ticket(q, "ticket> ")
    if i == -1:
        print("Cancelled.")
        return
    t = q[0][i]
    p = ask_priority("new priority")
    if p == 0:
        print("Cancelled.")
        return
    if p == t[0]:
        print("#" + str(t[1]) + " is " + level_name(p) + " already.")
        return
    was = level_name(t[0])
    heap.reprioritized(q[0], i, p)
    print("#" + str(t[1]) + " " + t[2] + ": " + was + " -> " + level_name(p) + ", now number " + str(place_in_line(q, t[1])) + " in line.")

def cancel(q):
    if len(q[0]) == 0:
        print("No ticket is waiting.")
        return
    i = ask_ticket(q, "ticket to cancel> ")
    if i == -1:
        print("Cancelled.")
        return
    res = heap.removed_at(q[0], i)
    q[0] = res[1]
    print("Cancelled " + ticket(res[0]) + ". " + counted(len(q[0]), "ticket", "tickets") + " still waiting.")

def summary(q):
    counts = [0, 0, 0, 0]
    for t in q[0]:
        counts[t[0] - 1] = counts[t[0] - 1] + 1
    print("priority  waiting  served")
    for p in range(1, 5):
        name = level_name(p)
        while len(name) < 10:
            name = name + " "
        w = str(counts[p - 1])
        while len(w) < 7:
            w = " " + w
        s = str(q[2][p - 1])
        while len(s) < 8:
            s = " " + s
        line = name + w + s
        if counts[p - 1] > 0:
            line = line + "  " + "#" * counts[p - 1]
        print(line)
    print("Waiting " + str(len(q[0])) + ", served " + str(q[2][0] + q[2][1] + q[2][2] + q[2][3]) + ", next ticket number #" + str(q[1]) + ".")

def sample(q):
    for t in [["Printer out of toner", 4], ["Cannot log in to email", 2], ["Server room too warm", 1], ["New laptop for Maria", 3], ["VPN drops every hour", 2], ["Update the wiki page", 4], ["Payroll system down", 1], ["Monitor flickers", 3]]:
        add(q, t[0], t[1])

print("== Help desk ==")
print("Tickets are served most urgent first, and in the order they came within a priority.")
q = [[], 1, [0, 0, 0, 0]]
running = True
while running:
    print("")
    print("1) new ticket  2) serve next  3) waiting  4) change priority  5) cancel  6) summary  7) sample  8) quit")
    choice = trim(input("choice> "))
    if choice == "1":
        new_ticket(q)
    elif choice == "2":
        serve(q)
    elif choice == "3":
        waiting(q)
    elif choice == "4":
        change(q)
    elif choice == "5":
        cancel(q)
    elif choice == "6":
        summary(q)
    elif choice == "7":
        if len(q[0]) + 8 > 30:
            print("The sample would take the queue past 30 tickets.")
        else:
            first = q[1]
            sample(q)
            print("Opened 8 sample tickets, #" + str(first) + " to #" + str(q[1] - 1) + ".")
    elif choice == "8":
        running = False
    else:
        print("Pick a number from 1 to 8.")
print("Bye.")
```

### heap.eml

```eml
# P053 help-desk queue - the waiting tickets as a binary min-heap, the
# corpus case binary-heap's structure: no child pointers, the entry at index
# i has its children at 2i + 1 and 2i + 2. An entry is a ticket
# [priority, number, title]. One ticket comes before another when its
# priority is more urgent (a smaller number), or, at the same priority, when
# it was opened first (a smaller ticket number) - first come, first served
# within each priority.

def before(a, b):
    return a[0] < b[0] or (a[0] == b[0] and a[1] < b[1])

def swap(h, i, j):
    h[i] => t
    h[j] => h[i]
    t => h[j]

def sift_up(h, i):
    while i > 0:
        int((i - 1) / 2) => p
        if not before(h[i], h[p]):
            return
        swap(h, i, p)
        p => i

def sift_down(h, i):
    len(h) => n
    while True:
        2 * i + 1 => l
        l + 1 => r
        i => m
        if l < n and before(h[l], h[m]):
            l => m
        if r < n and before(h[r], h[m]):
            r => m
        if m == i:
            return
        swap(h, i, m)
        m => i

def push(h, entry):
    # Returns the heap with entry in it (the list grows by +, which makes a
    # new list, so the caller keeps the returned one).
    h + [entry] => h
    sift_up(h, len(h) - 1)
    return h

def removed_at(h, i):
    # Returns [the entry at index i, the heap without it]. The last entry
    # moves into the gap and sifts up or down - only one of the two moves it.
    h[i] => out
    h[len(h) - 1] => last
    h[0:len(h) - 1] => h
    if i < len(h):
        last => h[i]
        sift_up(h, i)
        sift_down(h, i)
    return [out, h]

def pop(h):
    return removed_at(h, 0)

def find(h, number):
    for i in [0:len(h) - 1]:
        if h[i][1] == number:
            return i
    return -1

def reprioritized(h, i, priority):
    priority => h[i][0]
    sift_up(h, i)
    sift_down(h, i)

def service_order(h):
    # The waiting tickets in the order they will be served: popped one by one
    # from a copy, so the queue itself is left alone.
    h[0:len(h)] => c
    [] => out
    while len(c) > 0:
        pop(c) => res
        out + [res[0]] => out
        res[1] => c
    return out
```

Python projection of heap.eml:

```python
def before(a, b):
    return a[0] < b[0] or a[0] == b[0] and a[1] < b[1]

def swap(h, i, j):
    t = h[i]
    h[i] = h[j]
    h[j] = t

def sift_up(h, i):
    while i > 0:
        p = int((i - 1) / 2)
        if not before(h[i], h[p]):
            return
        swap(h, i, p)
        i = p

def sift_down(h, i):
    n = len(h)
    while True:
        l = 2 * i + 1
        r = l + 1
        m = i
        if l < n and before(h[l], h[m]):
            m = l
        if r < n and before(h[r], h[m]):
            m = r
        if m == i:
            return
        swap(h, i, m)
        i = m

def push(h, entry):
    h = h + [entry]
    sift_up(h, len(h) - 1)
    return h

def removed_at(h, i):
    out = h[i]
    last = h[len(h) - 1]
    h = h[0:len(h) - 1]
    if i < len(h):
        h[i] = last
        sift_up(h, i)
        sift_down(h, i)
    return [out, h]

def pop(h):
    return removed_at(h, 0)

def find(h, number):
    for i in range(0, len(h)):
        if h[i][1] == number:
            return i
    return -1

def reprioritized(h, i, priority):
    h[i][0] = priority
    sift_up(h, i)
    sift_down(h, i)

def service_order(h):
    c = h[0:len(h)]
    out = []
    while len(c) > 0:
        res = pop(c)
        out = out + [res[0]]
        c = res[1]
    return out
```

## README

# P053 - Help desk queue

Support tickets wait in one queue and are served most urgent first, and,
within each priority, in the order they were opened. Open tickets with one
of four priorities - urgent, high, normal, low - serve the next one, see
the whole line, change a waiting ticket's priority or cancel it, and see
the queue grouped by priority. At most 30 tickets wait at a time.

- `main.eml` - the menu, reading tickets and answers, and the messages
- `heap.eml` - the queue: a binary heap with pushing, serving, removing
  any ticket, a priority change, and the serving order

How each part works:

- The waiting tickets are a binary min-heap, as in the corpus case
  `binary-heap`: one list, with no links between entries, where the entry
  at index i has its children at 2i + 1 and 2i + 2. Every parent comes
  before its children, so the root is always the next ticket to serve.
- "Comes before" compares the priority first and then the ticket number.
  Numbers only grow, so at the same priority the older ticket goes first.
  Without that second rule a heap would serve equal priorities in no
  particular order.
- A new ticket goes at the end and moves up while it comes before its
  parent. Serving takes the root, puts the last entry there, and moves it
  down below the smaller child until neither child comes before it.
  Cancelling a ticket from the middle does the same at that index; a
  priority change moves the ticket up or down from where it is. Each of
  these touches one path from the root to a leaf, never the whole queue.
- The waiting line is worked out by serving a copy of the heap to the
  end, so the queue itself is left as it was. The summary puts the waiting
  tickets in one bucket per priority, as the corpus case
  `task-priority-bucketer` sorts tasks into buckets, next to how many of
  each priority have been served.

What is checked: menu choices 1 to 8; a title of up to 40 characters; a
priority from 1 to 4; a ticket number, with or without #, that is
waiting; at most 30 waiting tickets. An empty answer cancels.

Sessions: `sessions/basic.in` opens the eight sample tickets and shows
the line: two urgent first, in the order they came, then two high, two
normal and two low.
- The two urgent tickets are served.
- #8 is raised from normal to urgent and goes to the front.
- #6 is cancelled.
- A new high ticket, #9, goes in behind the two high tickets already
  waiting, number 4 in line.
- The summary has one bucket per priority. Two more tickets are served and
  the line is shown again.

`sessions/bad-input.in` gives:
- menu choices 0 and x;
- every action on the empty queue;
- an empty title, a title over 40 characters, and priorities 5, x and an
  empty one;
- tickets x and #99, a change to the priority a ticket already has, and a
  change and a cancel that are cancelled;
- the sample three times, a fourth that would pass 30 tickets, five more
  tickets up to 30, one too many, and the summary of the full queue.

Built on the verified corpus cases `binary-heap` (a min-heap with sift up
and sift down in one list) and `task-priority-bucketer` (tasks sorted into
priority buckets).
