{
  "id": "P141",
  "slug": "event-tickets",
  "key": "P141-event-tickets",
  "title": "Event tickets",
  "summary": "One concert's bookings kept by two designs side by side - cancel as an undo and cancel as a state - so a cancel sent twice gives back seats and money twice in one of them, and the extra seats get sold.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P141 - Event tickets\n\nOne concert's bookings, kept by two designs side by side: a cancel that\nundoes what the booking did, and a cancel that sets the booking to\ncancelled.\n\n- `main.eml` - the menu, the bookings in each design, booking seats,\n  cancelling with the request sent one to three times, and the checks\n- `tickets.eml` - the hall, the opening bookings, booking, the two cancels,\n  the seats held, what the cancelled bookings paid, and the three checks\n\nHow each part works:\n\n- The hall has 10 seats at 25.00. Ana, Ben and Cleo have booked 2, 1 and 4.\n- As in the corpus case `cancel-is-not-idempotent`, the undo design's cancel\n  is a delta: it gives the seats back and refunds what was paid, every time\n  it is sent. The state design's cancel makes the booking cancelled, and a\n  booking that already is stays as it is.\n- One cancel of a booking still held does the same in both designs. A\n  cancel sent three times - a slow reply, a click again, a retry - gives\n  back the seats and the money three times in the undo design: Ben's one\n  seat becomes three free seats and 75.00 refunded.\n- The extra seats can be sold. Once the undo design has more seats free\n  than the hall has, it takes bookings the state design refuses, and holds\n  12 seats in a hall of 10; later the state design can take one the undo\n  design has no room for.\n- The checks: free seats plus seats held equal the hall, and the refunds\n  equal what the cancelled bookings paid. The state design always passes;\n  the undo design is off by exactly the seats and payments of every cancel\n  after the first. Asking whether each booking says cancelled passes in\n  both and cannot tell them apart.\n\nWhat is checked: menu choices 1 to 5; a name of 1 to 12 letters and 1 to 4\nseats; a booking number that exists in at least one design and a cancel\nsent 1 to 3 times. An empty answer cancels.\n\nSessions: `sessions/basic.in` gives:\n- the bookings; Ana's booking cancelled once, and Ben's with the cancel sent\n  three times; the checks;\n- Dan, Eve and Fay booking 4, 4 and 2 seats: in both designs, in the undo\n  design only, and in the state design only; the bookings and the checks.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- booking: a cancel, the name Ann3, then Gus with a cancel, 0, 5 and x seats;\n- cancelling: a cancel, x, booking 9, then booking 1 with a cancel, 0, 4\n  and x sends;\n- Ana's booking cancelled once and then once more; the checks and the\n  bookings.\n\nBuilt on the verified corpus case `cancel-is-not-idempotent` (cancel as a\ndelta and cancel as a state, applied one, two and three times).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P141 event tickets: one concert's bookings kept by two designs side by\n# side - cancel as an undo and cancel as a state - booking seats, cancelling\n# with the request sent one to three times, and the checks on each.\nimport tickets\n\ndef plain(s):\n    0 => i\n    len(s) => j\n    while i < j and s[i] == \" \":\n        i + 1 => i\n    while j > i and s[j - 1] == \" \":\n        j - 1 => j\n    return s[i:j]\n\ndef pad(s, n):\n    s => out\n    while len(out) < n:\n        out + \" \" => out\n    return out\n\ndef lpad(s, n):\n    s => out\n    while len(out) < n:\n        \" \" + out => out\n    return out\n\ndef money(c):\n    c % 100 => p\n    if p < 10:\n        return str(int((c - p) / 100)) + \".0\" + str(p)\n    return str(int((c - p) / 100)) + \".\" + str(p)\n\ndef seats_word(n):\n    if n == 1:\n        return \"1 seat\"\n    return str(n) + \" seats\"\n\ndef times_word(n):\n    if n == 1:\n        return \"1 time\"\n    return str(n) + \" times\"\n\ndef digits(t, most):\n    if len(t) == 0 or len(t) > most:\n        return False\n    for c in t:\n        if c < \"0\" or c > \"9\":\n            return False\n    return True\n\ndef letters(t):\n    if len(t) == 0 or len(t) > 12:\n        return False\n    for c in t:\n        if not ((c >= \"a\" and c <= \"z\") or (c >= \"A\" and c <= \"Z\")):\n            return False\n    return True\n\ndef show_design(label, led):\n    (label + \": \" + seats_word(led[0]) + \" free, \" + money(led[1]) + \" refunded.\") ^0\n    for b in led[2]:\n        (\"  \" + lpad(str(b[0]), 2) + \"  \" + pad(b[1], 13) + pad(seats_word(b[2]), 9) + lpad(money(b[3]), 6) + \"  \" + b[4]) => line\n        if b[5] > 0:\n            line + \", \" + money(b[5]) + \" back\" => line\n        line ^0\n\ndef show(u, s):\n    tickets.venue() => v\n    (\"The hall has \" + str(v[0]) + \" seats at \" + money(v[1]) + \" each.\") ^0\n    show_design(\"Undo design\", u)\n    show_design(\"State design\", s)\n\ndef book(u, s, n):\n    plain(input(\"name> \")) => name\n    if name == \"\":\n        \"Cancelled.\" ^0\n        return [u, s, n]\n    if not letters(name):\n        \"Type a name of 1 to 12 letters.\" ^0\n        return [u, s, n]\n    plain(input(\"seats (1 to 4)> \")) => t\n    if t == \"\":\n        \"Cancelled.\" ^0\n        return [u, s, n]\n    if not digits(t, 1) or int(t) < 1 or int(t) > 4:\n        \"Type a number of seats from 1 to 4.\" ^0\n        return [u, s, n]\n    int(t) => k\n    tickets.book(u, n, name, k) => u2\n    tickets.book(s, n, name, k) => s2\n    len(u2[2]) > len(u[2]) => in_u\n    len(s2[2]) > len(s[2]) => in_s\n    (\"Booking \" + str(n) + \": \" + name + \", \" + seats_word(k) + \", \" + money(k * tickets.venue()[1])) => head\n    if in_u and in_s:\n        (head + \", in both designs.\") ^0\n    elif in_u:\n        (head + \" - taken by the undo design only; the state design has \" + str(s[0]) + \" free.\") ^0\n        tickets.held(u2) => h\n        if h > tickets.venue()[0]:\n            (\"The undo design now holds \" + str(h) + \" seats in a hall of \" + str(tickets.venue()[0]) + \".\") ^0\n    elif in_s:\n        (head + \" - taken by the state design only; the undo design has \" + str(u[0]) + \" free.\") ^0\n    else:\n        if u[0] == s[0]:\n            (\"Not enough seats: \" + str(s[0]) + \" free in both designs.\") ^0\n        else:\n            (\"Not enough seats: the undo design has \" + str(u[0]) + \" free, the state design \" + str(s[0]) + \".\") ^0\n        return [u, s, n]\n    return [u2, s2, n + 1]\n\ndef effect(label, before, after):\n    (\"  \" + label + \"seats free \" + str(before[0]) + \" -> \" + str(after[0]) + \", refunded \" + money(before[1]) + \" -> \" + money(after[1])) ^0\n\ndef cancel(u, s):\n    plain(input(\"booking number> \")) => t\n    if t == \"\":\n        \"Cancelled.\" ^0\n        return [u, s]\n    if not digits(t, 3):\n        \"Type a booking number.\" ^0\n        return [u, s]\n    int(t) => n\n    tickets.find(u, n) => ku\n    tickets.find(s, n) => ks\n    if ku < 0 and ks < 0:\n        (\"There is no booking \" + str(n) + \".\") ^0\n        return [u, s]\n    plain(input(\"times the cancel is sent (1 to 3)> \")) => r\n    if r == \"\":\n        \"Cancelled.\" ^0\n        return [u, s]\n    if not digits(r, 1) or int(r) < 1 or int(r) > 3:\n        \"Type a number from 1 to 3.\" ^0\n        return [u, s]\n    int(r) => times\n    if ku >= 0:\n        u[2][ku] => b\n    else:\n        s[2][ks] => b\n    (\"Booking \" + str(n) + \" (\" + b[1] + \", \" + seats_word(b[2]) + \", \" + money(b[3]) + \" paid), the cancel sent \" + times_word(times) + \":\") ^0\n    u => u2\n    s => s2\n    0 => i\n    while i < times:\n        if ku >= 0:\n            tickets.cancel_undo(u2, n) => u2\n        if ks >= 0:\n            tickets.cancel_state(s2, n) => s2\n        i + 1 => i\n    if ku >= 0:\n        effect(\"undo:  \", u, u2)\n    else:\n        (\"  undo:  no booking \" + str(n) + \" here; this design refused it.\") ^0\n    if ks >= 0:\n        effect(\"state: \", s, s2)\n    else:\n        (\"  state: no booking \" + str(n) + \" here; this design refused it.\") ^0\n    if ku >= 0 and ks >= 0:\n        u2[0] - u[0] => du\n        s2[0] - s[0] => ds\n        u2[1] - u[1] => mu\n        s2[1] - s[1] => ms\n        if s[2][ks][4] == \"cancelled\":\n            (\"Booking \" + str(n) + \" was already cancelled. The state design changes nothing; the undo design gave back \" + seats_word(du) + \" and \" + money(mu) + \" again.\") ^0\n        elif du == ds and mu == ms:\n            \"One cancel of a booking still held, and the two designs agree.\" ^0\n        else:\n            (\"Both say booking \" + str(n) + \" is cancelled. The undo design gave back \" + seats_word(du - ds) + \" and \" + money(mu - ms) + \" more than the state design: each send undid the booking again.\") ^0\n    return [u2, s2]\n\ndef yes(ok):\n    if ok:\n        return \"yes\"\n    return \"no\"\n\ndef checks(u, s):\n    tickets.venue()[0] => cap\n    \"Checks on each design:                          undo   state\" ^0\n    (\"  \" + pad(\"seats free + seats held = \" + str(cap), 46) + pad(yes(tickets.seats_ok(u)), 7) + yes(tickets.seats_ok(s))) ^0\n    (\"  \" + pad(\"refunded = what cancelled bookings paid\", 46) + pad(yes(tickets.refunds_ok(u)), 7) + yes(tickets.refunds_ok(s))) ^0\n    (\"  \" + pad(\"every booking sent a cancel says cancelled\", 46) + pad(yes(tickets.statuses_ok(u)), 7) + yes(tickets.statuses_ok(s))) ^0\n    tickets.seats_ok(u) and tickets.refunds_ok(u) => u_ok\n    tickets.seats_ok(s) and tickets.refunds_ok(s) => s_ok\n    if u_ok and s_ok:\n        \"Both designs pass every check.\" ^0\n    else:\n        if not tickets.seats_ok(u):\n            (\"The undo design: \" + str(u[0]) + \" free + \" + str(tickets.held(u)) + \" held = \" + str(u[0] + tickets.held(u)) + \" seats in a hall of \" + str(cap) + \".\") ^0\n        if not tickets.refunds_ok(u):\n            (\"The undo design: \" + money(u[1]) + \" refunded against \" + money(tickets.owed_back(u)) + \" paid by the cancelled bookings.\") ^0\n        if tickets.statuses_ok(u) and tickets.statuses_ok(s):\n            \"Asking whether each booking is cancelled cannot tell the designs apart; only the counts can.\" ^0\n\ntickets.opening_ledger() => u\ntickets.opening_ledger() => s\nlen(tickets.opening()) + 1 => next_number\n\n\"== Event tickets ==\" ^0\n\"One concert's bookings, kept twice: cancel as an undo, and cancel as a state.\" ^0\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\" ^0\n    plain(input(\"choice> \")) => choice\n    if choice == \"1\":\n        show(u, s)\n    elif choice == \"2\":\n        book(u, s, next_number) => r\n        r[0] => u\n        r[1] => s\n        r[2] => next_number\n    elif choice == \"3\":\n        cancel(u, s) => r\n        r[0] => u\n        r[1] => s\n    elif choice == \"4\":\n        checks(u, s)\n    elif choice == \"5\":\n        False => running\n    else:\n        \"Pick a number from 1 to 5.\" ^0\n\"Bye.\" ^0\n",
      "python": "import tickets\n\ndef plain(s):\n    i = 0\n    j = len(s)\n    while i < j and s[i] == \" \":\n        i = i + 1\n    while j > i and s[j - 1] == \" \":\n        j = j - 1\n    return s[i:j]\n\ndef pad(s, n):\n    out = s\n    while len(out) < n:\n        out = out + \" \"\n    return out\n\ndef lpad(s, n):\n    out = s\n    while len(out) < n:\n        out = \" \" + out\n    return out\n\ndef money(c):\n    p = c % 100\n    if p < 10:\n        return str(int((c - p) / 100)) + \".0\" + str(p)\n    return str(int((c - p) / 100)) + \".\" + str(p)\n\ndef seats_word(n):\n    if n == 1:\n        return \"1 seat\"\n    return str(n) + \" seats\"\n\ndef times_word(n):\n    if n == 1:\n        return \"1 time\"\n    return str(n) + \" times\"\n\ndef digits(t, most):\n    if len(t) == 0 or len(t) > most:\n        return False\n    for c in t:\n        if c < \"0\" or c > \"9\":\n            return False\n    return True\n\ndef letters(t):\n    if len(t) == 0 or len(t) > 12:\n        return False\n    for c in t:\n        if not (c >= \"a\" and c <= \"z\" or c >= \"A\" and c <= \"Z\"):\n            return False\n    return True\n\ndef show_design(label, led):\n    print(label + \": \" + seats_word(led[0]) + \" free, \" + money(led[1]) + \" refunded.\")\n    for b in led[2]:\n        line = \"  \" + lpad(str(b[0]), 2) + \"  \" + pad(b[1], 13) + pad(seats_word(b[2]), 9) + lpad(money(b[3]), 6) + \"  \" + b[4]\n        if b[5] > 0:\n            line = line + \", \" + money(b[5]) + \" back\"\n        print(line)\n\ndef show(u, s):\n    v = tickets.venue()\n    print(\"The hall has \" + str(v[0]) + \" seats at \" + money(v[1]) + \" each.\")\n    show_design(\"Undo design\", u)\n    show_design(\"State design\", s)\n\ndef book(u, s, n):\n    name = plain(input(\"name> \"))\n    if name == \"\":\n        print(\"Cancelled.\")\n        return [u, s, n]\n    if not letters(name):\n        print(\"Type a name of 1 to 12 letters.\")\n        return [u, s, n]\n    t = plain(input(\"seats (1 to 4)> \"))\n    if t == \"\":\n        print(\"Cancelled.\")\n        return [u, s, n]\n    if not digits(t, 1) or int(t) < 1 or int(t) > 4:\n        print(\"Type a number of seats from 1 to 4.\")\n        return [u, s, n]\n    k = int(t)\n    u2 = tickets.book(u, n, name, k)\n    s2 = tickets.book(s, n, name, k)\n    in_u = len(u2[2]) > len(u[2])\n    in_s = len(s2[2]) > len(s[2])\n    head = \"Booking \" + str(n) + \": \" + name + \", \" + seats_word(k) + \", \" + money(k * tickets.venue()[1])\n    if in_u and in_s:\n        print(head + \", in both designs.\")\n    elif in_u:\n        print(head + \" - taken by the undo design only; the state design has \" + str(s[0]) + \" free.\")\n        h = tickets.held(u2)\n        if h > tickets.venue()[0]:\n            print(\"The undo design now holds \" + str(h) + \" seats in a hall of \" + str(tickets.venue()[0]) + \".\")\n    elif in_s:\n        print(head + \" - taken by the state design only; the undo design has \" + str(u[0]) + \" free.\")\n    else:\n        if u[0] == s[0]:\n            print(\"Not enough seats: \" + str(s[0]) + \" free in both designs.\")\n        else:\n            print(\"Not enough seats: the undo design has \" + str(u[0]) + \" free, the state design \" + str(s[0]) + \".\")\n        return [u, s, n]\n    return [u2, s2, n + 1]\n\ndef effect(label, before, after):\n    print(\"  \" + label + \"seats free \" + str(before[0]) + \" -> \" + str(after[0]) + \", refunded \" + money(before[1]) + \" -> \" + money(after[1]))\n\ndef cancel(u, s):\n    t = plain(input(\"booking number> \"))\n    if t == \"\":\n        print(\"Cancelled.\")\n        return [u, s]\n    if not digits(t, 3):\n        print(\"Type a booking number.\")\n        return [u, s]\n    n = int(t)\n    ku = tickets.find(u, n)\n    ks = tickets.find(s, n)\n    if ku < 0 and ks < 0:\n        print(\"There is no booking \" + str(n) + \".\")\n        return [u, s]\n    r = plain(input(\"times the cancel is sent (1 to 3)> \"))\n    if r == \"\":\n        print(\"Cancelled.\")\n        return [u, s]\n    if not digits(r, 1) or int(r) < 1 or int(r) > 3:\n        print(\"Type a number from 1 to 3.\")\n        return [u, s]\n    times = int(r)\n    if ku >= 0:\n        b = u[2][ku]\n    else:\n        b = s[2][ks]\n    print(\"Booking \" + str(n) + \" (\" + b[1] + \", \" + seats_word(b[2]) + \", \" + money(b[3]) + \" paid), the cancel sent \" + times_word(times) + \":\")\n    u2 = u\n    s2 = s\n    i = 0\n    while i < times:\n        if ku >= 0:\n            u2 = tickets.cancel_undo(u2, n)\n        if ks >= 0:\n            s2 = tickets.cancel_state(s2, n)\n        i = i + 1\n    if ku >= 0:\n        effect(\"undo:  \", u, u2)\n    else:\n        print(\"  undo:  no booking \" + str(n) + \" here; this design refused it.\")\n    if ks >= 0:\n        effect(\"state: \", s, s2)\n    else:\n        print(\"  state: no booking \" + str(n) + \" here; this design refused it.\")\n    if ku >= 0 and ks >= 0:\n        du = u2[0] - u[0]\n        ds = s2[0] - s[0]\n        mu = u2[1] - u[1]\n        ms = s2[1] - s[1]\n        if s[2][ks][4] == \"cancelled\":\n            print(\"Booking \" + str(n) + \" was already cancelled. The state design changes nothing; the undo design gave back \" + seats_word(du) + \" and \" + money(mu) + \" again.\")\n        elif du == ds and mu == ms:\n            print(\"One cancel of a booking still held, and the two designs agree.\")\n        else:\n            print(\"Both say booking \" + str(n) + \" is cancelled. The undo design gave back \" + seats_word(du - ds) + \" and \" + money(mu - ms) + \" more than the state design: each send undid the booking again.\")\n    return [u2, s2]\n\ndef yes(ok):\n    if ok:\n        return \"yes\"\n    return \"no\"\n\ndef checks(u, s):\n    cap = tickets.venue()[0]\n    print(\"Checks on each design:                          undo   state\")\n    print(\"  \" + pad(\"seats free + seats held = \" + str(cap), 46) + pad(yes(tickets.seats_ok(u)), 7) + yes(tickets.seats_ok(s)))\n    print(\"  \" + pad(\"refunded = what cancelled bookings paid\", 46) + pad(yes(tickets.refunds_ok(u)), 7) + yes(tickets.refunds_ok(s)))\n    print(\"  \" + pad(\"every booking sent a cancel says cancelled\", 46) + pad(yes(tickets.statuses_ok(u)), 7) + yes(tickets.statuses_ok(s)))\n    u_ok = tickets.seats_ok(u) and tickets.refunds_ok(u)\n    s_ok = tickets.seats_ok(s) and tickets.refunds_ok(s)\n    if u_ok and s_ok:\n        print(\"Both designs pass every check.\")\n    else:\n        if not tickets.seats_ok(u):\n            print(\"The undo design: \" + str(u[0]) + \" free + \" + str(tickets.held(u)) + \" held = \" + str(u[0] + tickets.held(u)) + \" seats in a hall of \" + str(cap) + \".\")\n        if not tickets.refunds_ok(u):\n            print(\"The undo design: \" + money(u[1]) + \" refunded against \" + money(tickets.owed_back(u)) + \" paid by the cancelled bookings.\")\n        if tickets.statuses_ok(u) and tickets.statuses_ok(s):\n            print(\"Asking whether each booking is cancelled cannot tell the designs apart; only the counts can.\")\n\nu = tickets.opening_ledger()\ns = tickets.opening_ledger()\nnext_number = len(tickets.opening()) + 1\nprint(\"== Event tickets ==\")\nprint(\"One concert's bookings, kept twice: cancel as an undo, and cancel as a state.\")\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\")\n    choice = plain(input(\"choice> \"))\n    if choice == \"1\":\n        show(u, s)\n    elif choice == \"2\":\n        r = book(u, s, next_number)\n        u = r[0]\n        s = r[1]\n        next_number = r[2]\n    elif choice == \"3\":\n        r = cancel(u, s)\n        u = r[0]\n        s = r[1]\n    elif choice == \"4\":\n        checks(u, s)\n    elif choice == \"5\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 5.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "tickets.eml",
      "eml": "# P141 event tickets - seats booked for one concert and cancelled two ways.\n# As in the corpus case cancel-is-not-idempotent, a cancel written as \"undo\n# what the booking did\" (give the seats back, refund what was paid) is a\n# delta: sent twice, it undoes twice. A cancel written as \"this booking is\n# cancelled\" is a state: reaching it again changes nothing. A ledger is\n# [seats free, cents refunded, bookings]; a booking is [number, name, seats,\n# cents paid, status, cents refunded, cancels received].\n\ndef venue():\n    # [seats in the hall, price of a seat in cents]\n    return [10, 2500]\n\ndef opening():\n    # the bookings already made: [name, seats]\n    return [[\"Ana\", 2], [\"Ben\", 1], [\"Cleo\", 4]]\n\ndef find(led, number):\n    0 => i\n    for b in led[2]:\n        if b[0] == number:\n            return i\n        i + 1 => i\n    return 0 - 1\n\ndef book(led, number, name, seats):\n    # the ledger with the booking added, or the same ledger when there are\n    # not enough seats free\n    if seats > led[0]:\n        return led\n    seats * venue()[1] => paid\n    return [led[0] - seats, led[1], led[2] + [[number, name, seats, paid, \"confirmed\", 0, 0]]]\n\ndef cancel_undo(led, number):\n    # undo the booking: its seats back, what it paid refunded, every time\n    find(led, number) => k\n    led[0] => free\n    led[1] => refunded\n    [] => out\n    0 => i\n    for b in led[2]:\n        if i == k:\n            free + b[2] => free\n            refunded + b[3] => refunded\n            out + [[b[0], b[1], b[2], b[3], \"cancelled\", b[5] + b[3], b[6] + 1]] => out\n        else:\n            out + [b] => out\n        i + 1 => i\n    return [free, refunded, out]\n\ndef cancel_state(led, number):\n    # make the booking cancelled; one that already is stays as it is\n    find(led, number) => k\n    led[0] => free\n    led[1] => refunded\n    [] => out\n    0 => i\n    for b in led[2]:\n        if i == k:\n            if b[4] == \"cancelled\":\n                out + [[b[0], b[1], b[2], b[3], b[4], b[5], b[6] + 1]] => out\n            else:\n                free + b[2] => free\n                refunded + b[3] => refunded\n                out + [[b[0], b[1], b[2], b[3], \"cancelled\", b[3], b[6] + 1]] => out\n        else:\n            out + [b] => out\n        i + 1 => i\n    return [free, refunded, out]\n\ndef opening_ledger():\n    [venue()[0], 0, []] => led\n    1 => n\n    for b in opening():\n        book(led, n, b[0], b[1]) => led\n        n + 1 => n\n    return led\n\ndef held(led):\n    # seats held by bookings that are not cancelled\n    0 => h\n    for b in led[2]:\n        if b[4] == \"confirmed\":\n            h + b[2] => h\n    return h\n\ndef owed_back(led):\n    # what the cancelled bookings paid: all that should ever be refunded\n    0 => t\n    for b in led[2]:\n        if b[4] == \"cancelled\":\n            t + b[3] => t\n    return t\n\ndef seats_ok(led):\n    return led[0] + held(led) == venue()[0]\n\ndef refunds_ok(led):\n    return led[1] == owed_back(led)\n\ndef statuses_ok(led):\n    # every booking that was sent a cancel says cancelled\n    for b in led[2]:\n        if b[6] > 0 and b[4] != \"cancelled\":\n            return False\n    return True\n",
      "python": "def venue():\n    return [10, 2500]\n\ndef opening():\n    return [[\"Ana\", 2], [\"Ben\", 1], [\"Cleo\", 4]]\n\ndef find(led, number):\n    i = 0\n    for b in led[2]:\n        if b[0] == number:\n            return i\n        i = i + 1\n    return 0 - 1\n\ndef book(led, number, name, seats):\n    if seats > led[0]:\n        return led\n    paid = seats * venue()[1]\n    return [led[0] - seats, led[1], led[2] + [[number, name, seats, paid, \"confirmed\", 0, 0]]]\n\ndef cancel_undo(led, number):\n    k = find(led, number)\n    free = led[0]\n    refunded = led[1]\n    out = []\n    i = 0\n    for b in led[2]:\n        if i == k:\n            free = free + b[2]\n            refunded = refunded + b[3]\n            out = out + [[b[0], b[1], b[2], b[3], \"cancelled\", b[5] + b[3], b[6] + 1]]\n        else:\n            out = out + [b]\n        i = i + 1\n    return [free, refunded, out]\n\ndef cancel_state(led, number):\n    k = find(led, number)\n    free = led[0]\n    refunded = led[1]\n    out = []\n    i = 0\n    for b in led[2]:\n        if i == k:\n            if b[4] == \"cancelled\":\n                out = out + [[b[0], b[1], b[2], b[3], b[4], b[5], b[6] + 1]]\n            else:\n                free = free + b[2]\n                refunded = refunded + b[3]\n                out = out + [[b[0], b[1], b[2], b[3], \"cancelled\", b[3], b[6] + 1]]\n        else:\n            out = out + [b]\n        i = i + 1\n    return [free, refunded, out]\n\ndef opening_ledger():\n    led = [venue()[0], 0, []]\n    n = 1\n    for b in opening():\n        led = book(led, n, b[0], b[1])\n        n = n + 1\n    return led\n\ndef held(led):\n    h = 0\n    for b in led[2]:\n        if b[4] == \"confirmed\":\n            h = h + b[2]\n    return h\n\ndef owed_back(led):\n    t = 0\n    for b in led[2]:\n        if b[4] == \"cancelled\":\n            t = t + b[3]\n    return t\n\ndef seats_ok(led):\n    return led[0] + held(led) == venue()[0]\n\ndef refunds_ok(led):\n    return led[1] == owed_back(led)\n\ndef statuses_ok(led):\n    for b in led[2]:\n        if b[6] > 0 and b[4] != \"cancelled\":\n            return False\n    return True\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n2\n\n2\nAnn3\n2\nGus\n\n2\nGus\n0\n2\nGus\n5\n2\nGus\nx\n3\n\n3\nx\n3\n9\n3\n1\n\n3\n1\n0\n3\n1\n4\n3\n1\nx\n3\n1\n1\n3\n1\n1\n4\n1\n5\n",
      "screen": "== Event tickets ==\nOne concert's bookings, kept twice: cancel as an undo, and cancel as a state.\n\n1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\nchoice> 0\nPick a number from 1 to 5.\n\n1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\nchoice> x\nPick a number from 1 to 5.\n\n1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\nchoice> 2\nname> \nCancelled.\n\n1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\nchoice> 2\nname> Ann3\nType a name of 1 to 12 letters.\n\n1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\nchoice> 2\nname> Gus\nseats (1 to 4)> \nCancelled.\n\n1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\nchoice> 2\nname> Gus\nseats (1 to 4)> 0\nType a number of seats from 1 to 4.\n\n1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\nchoice> 2\nname> Gus\nseats (1 to 4)> 5\nType a number of seats from 1 to 4.\n\n1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\nchoice> 2\nname> Gus\nseats (1 to 4)> x\nType a number of seats from 1 to 4.\n\n1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\nchoice> 3\nbooking number> \nCancelled.\n\n1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\nchoice> 3\nbooking number> x\nType a booking number.\n\n1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\nchoice> 3\nbooking number> 9\nThere is no booking 9.\n\n1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\nchoice> 3\nbooking number> 1\ntimes the cancel is sent (1 to 3)> \nCancelled.\n\n1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\nchoice> 3\nbooking number> 1\ntimes the cancel is sent (1 to 3)> 0\nType a number from 1 to 3.\n\n1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\nchoice> 3\nbooking number> 1\ntimes the cancel is sent (1 to 3)> 4\nType a number from 1 to 3.\n\n1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\nchoice> 3\nbooking number> 1\ntimes the cancel is sent (1 to 3)> x\nType a number from 1 to 3.\n\n1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\nchoice> 3\nbooking number> 1\ntimes the cancel is sent (1 to 3)> 1\nBooking 1 (Ana, 2 seats, 50.00 paid), the cancel sent 1 time:\n  undo:  seats free 3 -> 5, refunded 0.00 -> 50.00\n  state: seats free 3 -> 5, refunded 0.00 -> 50.00\nOne cancel of a booking still held, and the two designs agree.\n\n1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\nchoice> 3\nbooking number> 1\ntimes the cancel is sent (1 to 3)> 1\nBooking 1 (Ana, 2 seats, 50.00 paid), the cancel sent 1 time:\n  undo:  seats free 5 -> 7, refunded 50.00 -> 100.00\n  state: seats free 5 -> 5, refunded 50.00 -> 50.00\nBooking 1 was already cancelled. The state design changes nothing; the undo design gave back 2 seats and 50.00 again.\n\n1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\nchoice> 4\nChecks on each design:                          undo   state\n  seats free + seats held = 10                  no     yes\n  refunded = what cancelled bookings paid       no     yes\n  every booking sent a cancel says cancelled    yes    yes\nThe undo design: 7 free + 5 held = 12 seats in a hall of 10.\nThe undo design: 100.00 refunded against 50.00 paid by the cancelled bookings.\nAsking whether each booking is cancelled cannot tell the designs apart; only the counts can.\n\n1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\nchoice> 1\nThe hall has 10 seats at 25.00 each.\nUndo design: 7 seats free, 100.00 refunded.\n   1  Ana          2 seats   50.00  cancelled, 100.00 back\n   2  Ben          1 seat    25.00  confirmed\n   3  Cleo         4 seats  100.00  confirmed\nState design: 5 seats free, 50.00 refunded.\n   1  Ana          2 seats   50.00  cancelled, 50.00 back\n   2  Ben          1 seat    25.00  confirmed\n   3  Cleo         4 seats  100.00  confirmed\n\n1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\nchoice> 5\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\n3\n1\n1\n3\n2\n3\n4\n2\nDan\n4\n2\nEve\n4\n2\nFay\n2\n1\n4\n5\n",
      "screen": "== Event tickets ==\nOne concert's bookings, kept twice: cancel as an undo, and cancel as a state.\n\n1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\nchoice> 1\nThe hall has 10 seats at 25.00 each.\nUndo design: 3 seats free, 0.00 refunded.\n   1  Ana          2 seats   50.00  confirmed\n   2  Ben          1 seat    25.00  confirmed\n   3  Cleo         4 seats  100.00  confirmed\nState design: 3 seats free, 0.00 refunded.\n   1  Ana          2 seats   50.00  confirmed\n   2  Ben          1 seat    25.00  confirmed\n   3  Cleo         4 seats  100.00  confirmed\n\n1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\nchoice> 3\nbooking number> 1\ntimes the cancel is sent (1 to 3)> 1\nBooking 1 (Ana, 2 seats, 50.00 paid), the cancel sent 1 time:\n  undo:  seats free 3 -> 5, refunded 0.00 -> 50.00\n  state: seats free 3 -> 5, refunded 0.00 -> 50.00\nOne cancel of a booking still held, and the two designs agree.\n\n1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\nchoice> 3\nbooking number> 2\ntimes the cancel is sent (1 to 3)> 3\nBooking 2 (Ben, 1 seat, 25.00 paid), the cancel sent 3 times:\n  undo:  seats free 5 -> 8, refunded 50.00 -> 125.00\n  state: seats free 5 -> 6, refunded 50.00 -> 75.00\nBoth say booking 2 is cancelled. The undo design gave back 2 seats and 50.00 more than the state design: each send undid the booking again.\n\n1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\nchoice> 4\nChecks on each design:                          undo   state\n  seats free + seats held = 10                  no     yes\n  refunded = what cancelled bookings paid       no     yes\n  every booking sent a cancel says cancelled    yes    yes\nThe undo design: 8 free + 4 held = 12 seats in a hall of 10.\nThe undo design: 125.00 refunded against 75.00 paid by the cancelled bookings.\nAsking whether each booking is cancelled cannot tell the designs apart; only the counts can.\n\n1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\nchoice> 2\nname> Dan\nseats (1 to 4)> 4\nBooking 4: Dan, 4 seats, 100.00, in both designs.\n\n1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\nchoice> 2\nname> Eve\nseats (1 to 4)> 4\nBooking 5: Eve, 4 seats, 100.00 - taken by the undo design only; the state design has 2 free.\nThe undo design now holds 12 seats in a hall of 10.\n\n1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\nchoice> 2\nname> Fay\nseats (1 to 4)> 2\nBooking 6: Fay, 2 seats, 50.00 - taken by the state design only; the undo design has 0 free.\n\n1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\nchoice> 1\nThe hall has 10 seats at 25.00 each.\nUndo design: 0 seats free, 125.00 refunded.\n   1  Ana          2 seats   50.00  cancelled, 50.00 back\n   2  Ben          1 seat    25.00  cancelled, 75.00 back\n   3  Cleo         4 seats  100.00  confirmed\n   4  Dan          4 seats  100.00  confirmed\n   5  Eve          4 seats  100.00  confirmed\nState design: 0 seats free, 75.00 refunded.\n   1  Ana          2 seats   50.00  cancelled, 50.00 back\n   2  Ben          1 seat    25.00  cancelled, 25.00 back\n   3  Cleo         4 seats  100.00  confirmed\n   4  Dan          4 seats  100.00  confirmed\n   6  Fay          2 seats   50.00  confirmed\n\n1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\nchoice> 4\nChecks on each design:                          undo   state\n  seats free + seats held = 10                  no     yes\n  refunded = what cancelled bookings paid       no     yes\n  every booking sent a cancel says cancelled    yes    yes\nThe undo design: 0 free + 12 held = 12 seats in a hall of 10.\nThe undo design: 125.00 refunded against 75.00 paid by the cancelled bookings.\nAsking whether each booking is cancelled cannot tell the designs apart; only the counts can.\n\n1) bookings  2) book seats  3) cancel a booking  4) checks  5) quit\nchoice> 5\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "cancel-is-not-idempotent",
      "caseId": "310-cancel-is-not-idempotent",
      "title": "Cancel is not idempotent — both designs cancel correctly once"
    }
  ],
  "updated": "2026-10-18"
}
