{
  "id": "P121",
  "slug": "attendance",
  "key": "P121-attendance",
  "title": "Attendance register",
  "summary": "A mark for each student at each session - present, late, excused, absent or none - and attendance rates under four ways of counting, which flag different students: an absence, a missing mark and no sessions yet are three different nothings.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P121 - Attendance register\n\nA mark for each student at each session: present, late, excused or\nabsent - or no mark at all, when the register was not taken or the\nstudent had not joined yet. Four ways of counting give four attendance\nrates, and they flag different students.\n\n- `main.eml` - the menu, the register, the rates and who is below 80%,\n  taking a register, correcting a mark, and a new student\n- `roll.eml` - the register, the four rates, percentages to a tenth, the\n  flags, the sessions not taken, and the changes\n\nHow each part works:\n\n- A rate is present or late over one of four counts of sessions:\n  1. every session held;\n  2. the sessions since the student joined;\n  3. the sessions with a mark;\n  4. those, leaving out the excused ones.\n  Percentages are rounded half up to a tenth, in whole numbers only.\n- As in the corpus case `absent-vs-empty-vs-zero`, an absence, a missing\n  mark and a student with no sessions yet are three different things:\n  - counting (1) or (2), a session nobody marked lowers everyone, and (1)\n    counts Dana's sessions before joining as absences: 50.0% against\n    100.0%;\n  - a new student with no marked session has no rate at all; (1) would\n    give 0.0% and flag them.\n- The counts fall from (1) to (4) and the rates never drop, so each list\n  of flagged students contains the next; the program names those flagged\n  only when a session with no mark counts as an absence.\n- As in the corpus case `attendance-register`, the counts are a dict\n  updated one mark at a time.\n\nWhat is checked: menu choices 1 to 6; one mark per student, each P, L, E\nor A in either case, or skip; a student on the register, a session from\nthe one they joined to the last, and a mark or none; a new name of up to\n10 letters, not already there, and at most 8 students. An empty answer\ncancels.\n\nSessions: `sessions/basic.in` gives:\n- the register (session 3 not taken, Dana joining at session 4) and the\n  rates: Ana and Dana flagged only when no mark counts as an absence;\n- Finn added, with no rate yet, and session 7 taken for all six;\n- Ana's session 3 corrected to P, and the rates; Cara's session 6 to no\n  mark, and the register and the rates.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- registers: a cancel, two marks, an X, and PP;\n- corrections: a cancel, Zed, then Ana with a cancel, 0, 9 and x for the\n  session, Dana's session 2 (before joining), and Ana's session 1 with a\n  cancel, Q and PP;\n- new students: a cancel, Ana, Jo-Jo and Bartholomews, then Gia, Hal and\n  Ivy, and Jon, one too many;\n- session 7 not taken, the register and the rates.\n\nBuilt on the verified corpus cases `attendance-register` (a dict of\ncounts kept across many small updates) and `absent-vs-empty-vs-zero`\n(three kinds of nothing, and a rule that treats them alike).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P121 attendance register: the register, attendance rates under four\n# ways of counting, taking a register, correcting a mark, a new student.\nimport roll\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 names_text(ns):\n    \"\" => out\n    for i in [0:len(ns) - 1]:\n        if i > 0 and i == len(ns) - 1:\n            out + \" and \" => out\n        elif i > 0:\n            out + \", \" => out\n        out + ns[i] => out\n    return out\n\ndef numbers_text(ns):\n    [] => out\n    for x in ns:\n        out + [str(x)] => out\n    return names_text(out)\n\ndef mark_of(c):\n    # a typed mark as P, L, E or A, or \"\" if it is none of them\n    if c == \"P\" or c == \"p\":\n        return \"P\"\n    if c == \"L\" or c == \"l\":\n        return \"L\"\n    if c == \"E\" or c == \"e\":\n        return \"E\"\n    if c == \"A\" or c == \"a\":\n        return \"A\"\n    return \"\"\n\ndef find(reg, name):\n    for i in [0:len(reg) - 1]:\n        if reg[i][0] == name:\n            return i\n    return 0 - 1\n\ndef show(reg):\n    roll.held(reg) => n\n    \"Register (P present, L late, E excused, A absent, - no mark):\" ^0\n    \"  session   \" => head\n    for k in [1:n]:\n        head + lpad(str(k), 3) => head\n    head ^0\n    for st in reg:\n        \"  \" + pad(st[0], 10) => line\n        for m in st[2]:\n            line + \"  \" + m => line\n        if st[1] > 1:\n            line + \"   joined at session \" + str(st[1]) => line\n        line ^0\n    roll.not_taken(reg) => gone\n    if len(gone) == 1:\n        (\"Session \" + str(gone[0]) + \": the register was not taken.\") ^0\n    elif len(gone) > 1:\n        (\"Sessions \" + numbers_text(gone) + \": the register was not taken.\") ^0\n\ndef rates(reg, line):\n    roll.held(reg) => n\n    (\"Present or late, out of: (1) every session held, (2) the sessions since joining, (3) the sessions with a mark, (4) those without excused ones.\") ^0\n    \"  \" + pad(\"student\", 10) => head\n    for k in [1:4]:\n        head + lpad(\"(\" + str(k) + \")\", 10) => head\n    head ^0\n    for st in reg:\n        \"  \" + pad(st[0], 10) => out\n        for p in roll.policies():\n            roll.rate(st, p, n) => r\n            out + lpad(roll.percent_text(r[0], r[1]), 10) => out\n        out ^0\n    (\"Below \" + str(line) + \"%:\") ^0\n    [] => lists\n    for k in [0:3]:\n        roll.flagged(reg, roll.policies()[k], line) => f\n        lists + [f] => lists\n        if len(f) == 0:\n            (\"  (\" + str(k + 1) + \") nobody\") ^0\n        else:\n            (\"  (\" + str(k + 1) + \") \" + names_text(f)) ^0\n    [] => only\n    for name in lists[0]:\n        False => in3\n        for x in lists[2]:\n            if x == name:\n                True => in3\n        if not in3:\n            only + [name] => only\n    if len(only) > 0:\n        (\"Flagged only when a session with no mark counts as an absence: \" + names_text(only) + \".\") ^0\n    [] => none\n    for st in reg:\n        if roll.rate(st, \"marked\", n)[1] == 0:\n            none + [st[0]] => none\n    if len(none) > 0:\n        (\"No marked session yet, so no rate: \" + names_text(none) + \". Counting every session held gives 0.0% instead.\") ^0\n\ndef take_register(reg):\n    [] => names\n    for st in reg:\n        names + [st[0]] => names\n    (\"Session \" + str(roll.held(reg) + 1) + \": a mark for each of \" + names_text(names) + \" in turn (P, L, E or A), or skip if the register was not taken.\") ^0\n    plain(input(\"marks> \")) => t\n    if t == \"\":\n        \"Cancelled.\" ^0\n        return reg\n    [] => marks\n    if t == \"skip\":\n        for st in reg:\n            marks + [\"-\"] => marks\n    else:\n        \"\" => w\n        for c in t + \" \":\n            if c == \" \":\n                if w != \"\":\n                    if len(w) != 1 or mark_of(w) == \"\":\n                        \"Each mark is P, L, E or A, separated by spaces.\" ^0\n                        return reg\n                    marks + [mark_of(w)] => marks\n                \"\" => w\n            else:\n                w + c => w\n        if len(marks) != len(reg):\n            (\"Type \" + str(len(reg)) + \" marks, one for each student.\") ^0\n            return reg\n    roll.take(reg, marks) => out\n    (\"Session \" + str(roll.held(out)) + \" recorded.\") ^0\n    return out\n\ndef correct_mark(reg):\n    plain(input(\"student> \")) => name\n    if name == \"\":\n        \"Cancelled.\" ^0\n        return reg\n    find(reg, name) => i\n    if i < 0:\n        \"No such student.\" ^0\n        return reg\n    plain(input(\"session (1 to \" + str(roll.held(reg)) + \")> \")) => t\n    if t == \"\":\n        \"Cancelled.\" ^0\n        return reg\n    0 => k\n    if len(t) > 0 and len(t) <= 2:\n        True => digits\n        for c in t:\n            if c < \"0\" or c > \"9\":\n                False => digits\n        if digits:\n            int(t) => k\n    if k < 1 or k > roll.held(reg):\n        (\"Type a session from 1 to \" + str(roll.held(reg)) + \".\") ^0\n        return reg\n    if k < reg[i][1]:\n        (name + \" joined at session \" + str(reg[i][1]) + \".\") ^0\n        return reg\n    plain(input(\"mark (P, L, E, A or none)> \")) => m\n    if m == \"\":\n        \"Cancelled.\" ^0\n        return reg\n    \"-\" => mark\n    if m != \"none\":\n        if len(m) != 1 or mark_of(m) == \"\":\n            \"Type P, L, E, A or none.\" ^0\n            return reg\n        mark_of(m) => mark\n    reg[i][2][k - 1] => before\n    roll.correct(reg, i, k, mark) => out\n    (name + \", session \" + str(k) + \": \" + before + \" -> \" + mark + \".\") ^0\n    return out\n\ndef add(reg):\n    plain(input(\"name (up to 10 letters)> \")) => name\n    if name == \"\":\n        \"Cancelled.\" ^0\n        return reg\n    if len(name) > 10:\n        \"Type a name of up to 10 letters.\" ^0\n        return reg\n    for c in name:\n        if not ((c >= \"a\" and c <= \"z\") or (c >= \"A\" and c <= \"Z\")):\n            \"Type a name of up to 10 letters.\" ^0\n            return reg\n    if find(reg, name) >= 0:\n        (name + \" is already on the register.\") ^0\n        return reg\n    if len(reg) >= 8:\n        \"8 students is the most.\" ^0\n        return reg\n    roll.add_student(reg, name) => out\n    (name + \" joins from session \" + str(roll.held(out) + 1) + \".\") ^0\n    return out\n\nroll.default_register() => reg\n80 => line\n\n\"== Attendance register ==\" ^0\n\"A mark for each student at each session: present, late, excused or absent - or no mark, when the register was not taken or the student had not joined.\" ^0\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\" ^0\n    plain(input(\"choice> \")) => choice\n    if choice == \"1\":\n        show(reg)\n    elif choice == \"2\":\n        rates(reg, line)\n    elif choice == \"3\":\n        take_register(reg) => reg\n    elif choice == \"4\":\n        correct_mark(reg) => reg\n    elif choice == \"5\":\n        add(reg) => reg\n    elif choice == \"6\":\n        False => running\n    else:\n        \"Pick a number from 1 to 6.\" ^0\n\"Bye.\" ^0\n",
      "python": "import roll\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 names_text(ns):\n    out = \"\"\n    for i in range(0, len(ns)):\n        if i > 0 and i == len(ns) - 1:\n            out = out + \" and \"\n        elif i > 0:\n            out = out + \", \"\n        out = out + ns[i]\n    return out\n\ndef numbers_text(ns):\n    out = []\n    for x in ns:\n        out = out + [str(x)]\n    return names_text(out)\n\ndef mark_of(c):\n    if c == \"P\" or c == \"p\":\n        return \"P\"\n    if c == \"L\" or c == \"l\":\n        return \"L\"\n    if c == \"E\" or c == \"e\":\n        return \"E\"\n    if c == \"A\" or c == \"a\":\n        return \"A\"\n    return \"\"\n\ndef find(reg, name):\n    for i in range(0, len(reg)):\n        if reg[i][0] == name:\n            return i\n    return 0 - 1\n\ndef show(reg):\n    n = roll.held(reg)\n    print(\"Register (P present, L late, E excused, A absent, - no mark):\")\n    head = \"  session   \"\n    for k in range(1, n+1):\n        head = head + lpad(str(k), 3)\n    print(head)\n    for st in reg:\n        line = \"  \" + pad(st[0], 10)\n        for m in st[2]:\n            line = line + \"  \" + m\n        if st[1] > 1:\n            line = line + \"   joined at session \" + str(st[1])\n        print(line)\n    gone = roll.not_taken(reg)\n    if len(gone) == 1:\n        print(\"Session \" + str(gone[0]) + \": the register was not taken.\")\n    elif len(gone) > 1:\n        print(\"Sessions \" + numbers_text(gone) + \": the register was not taken.\")\n\ndef rates(reg, line):\n    n = roll.held(reg)\n    print(\"Present or late, out of: (1) every session held, (2) the sessions since joining, (3) the sessions with a mark, (4) those without excused ones.\")\n    head = \"  \" + pad(\"student\", 10)\n    for k in range(1, 5):\n        head = head + lpad(\"(\" + str(k) + \")\", 10)\n    print(head)\n    for st in reg:\n        out = \"  \" + pad(st[0], 10)\n        for p in roll.policies():\n            r = roll.rate(st, p, n)\n            out = out + lpad(roll.percent_text(r[0], r[1]), 10)\n        print(out)\n    print(\"Below \" + str(line) + \"%:\")\n    lists = []\n    for k in range(0, 4):\n        f = roll.flagged(reg, roll.policies()[k], line)\n        lists = lists + [f]\n        if len(f) == 0:\n            print(\"  (\" + str(k + 1) + \") nobody\")\n        else:\n            print(\"  (\" + str(k + 1) + \") \" + names_text(f))\n    only = []\n    for name in lists[0]:\n        in3 = False\n        for x in lists[2]:\n            if x == name:\n                in3 = True\n        if not in3:\n            only = only + [name]\n    if len(only) > 0:\n        print(\"Flagged only when a session with no mark counts as an absence: \" + names_text(only) + \".\")\n    none = []\n    for st in reg:\n        if roll.rate(st, \"marked\", n)[1] == 0:\n            none = none + [st[0]]\n    if len(none) > 0:\n        print(\"No marked session yet, so no rate: \" + names_text(none) + \". Counting every session held gives 0.0% instead.\")\n\ndef take_register(reg):\n    names = []\n    for st in reg:\n        names = names + [st[0]]\n    print(\"Session \" + str(roll.held(reg) + 1) + \": a mark for each of \" + names_text(names) + \" in turn (P, L, E or A), or skip if the register was not taken.\")\n    t = plain(input(\"marks> \"))\n    if t == \"\":\n        print(\"Cancelled.\")\n        return reg\n    marks = []\n    if t == \"skip\":\n        for st in reg:\n            marks = marks + [\"-\"]\n    else:\n        w = \"\"\n        for c in t + \" \":\n            if c == \" \":\n                if w != \"\":\n                    if len(w) != 1 or mark_of(w) == \"\":\n                        print(\"Each mark is P, L, E or A, separated by spaces.\")\n                        return reg\n                    marks = marks + [mark_of(w)]\n                w = \"\"\n            else:\n                w = w + c\n        if len(marks) != len(reg):\n            print(\"Type \" + str(len(reg)) + \" marks, one for each student.\")\n            return reg\n    out = roll.take(reg, marks)\n    print(\"Session \" + str(roll.held(out)) + \" recorded.\")\n    return out\n\ndef correct_mark(reg):\n    name = plain(input(\"student> \"))\n    if name == \"\":\n        print(\"Cancelled.\")\n        return reg\n    i = find(reg, name)\n    if i < 0:\n        print(\"No such student.\")\n        return reg\n    t = plain(input(\"session (1 to \" + str(roll.held(reg)) + \")> \"))\n    if t == \"\":\n        print(\"Cancelled.\")\n        return reg\n    k = 0\n    if len(t) > 0 and len(t) <= 2:\n        digits = True\n        for c in t:\n            if c < \"0\" or c > \"9\":\n                digits = False\n        if digits:\n            k = int(t)\n    if k < 1 or k > roll.held(reg):\n        print(\"Type a session from 1 to \" + str(roll.held(reg)) + \".\")\n        return reg\n    if k < reg[i][1]:\n        print(name + \" joined at session \" + str(reg[i][1]) + \".\")\n        return reg\n    m = plain(input(\"mark (P, L, E, A or none)> \"))\n    if m == \"\":\n        print(\"Cancelled.\")\n        return reg\n    mark = \"-\"\n    if m != \"none\":\n        if len(m) != 1 or mark_of(m) == \"\":\n            print(\"Type P, L, E, A or none.\")\n            return reg\n        mark = mark_of(m)\n    before = reg[i][2][k - 1]\n    out = roll.correct(reg, i, k, mark)\n    print(name + \", session \" + str(k) + \": \" + before + \" -> \" + mark + \".\")\n    return out\n\ndef add(reg):\n    name = plain(input(\"name (up to 10 letters)> \"))\n    if name == \"\":\n        print(\"Cancelled.\")\n        return reg\n    if len(name) > 10:\n        print(\"Type a name of up to 10 letters.\")\n        return reg\n    for c in name:\n        if not (c >= \"a\" and c <= \"z\" or c >= \"A\" and c <= \"Z\"):\n            print(\"Type a name of up to 10 letters.\")\n            return reg\n    if find(reg, name) >= 0:\n        print(name + \" is already on the register.\")\n        return reg\n    if len(reg) >= 8:\n        print(\"8 students is the most.\")\n        return reg\n    out = roll.add_student(reg, name)\n    print(name + \" joins from session \" + str(roll.held(out) + 1) + \".\")\n    return out\n\nreg = roll.default_register()\nline = 80\nprint(\"== Attendance register ==\")\nprint(\"A mark for each student at each session: present, late, excused or absent - or no mark, when the register was not taken or the student had not joined.\")\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\")\n    choice = plain(input(\"choice> \"))\n    if choice == \"1\":\n        show(reg)\n    elif choice == \"2\":\n        rates(reg, line)\n    elif choice == \"3\":\n        reg = take_register(reg)\n    elif choice == \"4\":\n        reg = correct_mark(reg)\n    elif choice == \"5\":\n        reg = add(reg)\n    elif choice == \"6\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 6.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "roll.eml",
      "eml": "# P121 attendance register - students, sessions and a mark for each: P\n# present, L late, E excused, A absent, or no mark at all (the register was\n# not taken, or the student had not joined). As in the corpus case\n# absent-vs-empty-vs-zero, an absence, a missing mark and a student with no\n# sessions yet are three different nothings, and a rate that treats them\n# alike flags the wrong students. As in attendance-register, the counts are\n# a dict updated one mark at a time.\n#\n# A student is [name, session joined, marks]; marks holds one letter per\n# session held, \"-\" for no mark.\n\ndef default_register():\n    return [[\"Ana\", 1, [\"P\", \"P\", \"-\", \"L\", \"P\", \"A\"]], [\"Ben\", 1, [\"P\", \"A\", \"-\", \"P\", \"E\", \"P\"]], [\"Cara\", 1, [\"P\", \"P\", \"-\", \"P\", \"P\", \"L\"]], [\"Dana\", 4, [\"-\", \"-\", \"-\", \"P\", \"P\", \"P\"]], [\"Eli\", 1, [\"A\", \"L\", \"-\", \"E\", \"E\", \"A\"]]]\n\ndef held(reg):\n    if len(reg) == 0:\n        return 0\n    return len(reg[0][2])\n\ndef div(p, q):\n    # the whole part of p / q, for p >= 0 and q > 0\n    int(p / q) => f\n    while f * q > p:\n        f - 1 => f\n    while (f + 1) * q <= p:\n        f + 1 => f\n    return f\n\ndef counts(st):\n    # {letter: count} over a student's marks, \"-\" included\n    {\"P\": 0, \"L\": 0, \"E\": 0, \"A\": 0, \"-\": 0} => c\n    for m in st[2]:\n        c[m] + 1 => c[m]\n    return c\n\ndef policies():\n    return [\"all held\", \"since joining\", \"marked\", \"marked, not excused\"]\n\ndef rate(st, policy, n):\n    # [present or late, the sessions it is out of] under a policy\n    counts(st) => c\n    c[\"P\"] + c[\"L\"] => present\n    if policy == \"all held\":\n        return [present, n]\n    if policy == \"since joining\":\n        return [present, n - st[1] + 1]\n    if policy == \"marked\":\n        return [present, c[\"P\"] + c[\"L\"] + c[\"E\"] + c[\"A\"]]\n    return [present, c[\"P\"] + c[\"L\"] + c[\"A\"]]\n\ndef tenths(p, q):\n    # p / q as a percentage in tenths, rounded half up\n    return div(2000 * p + q, 2 * q)\n\ndef percent_text(p, q):\n    if q <= 0:\n        return \"-\"\n    tenths(p, q) => t\n    return str(div(t, 10)) + \".\" + str(t - 10 * div(t, 10)) + \"%\"\n\ndef below(p, q, line):\n    # under the line (a whole percent): only when there are sessions to count\n    return q > 0 and 100 * p < line * q\n\ndef flagged(reg, policy, line):\n    held(reg) => n\n    [] => out\n    for st in reg:\n        rate(st, policy, n) => r\n        if below(r[0], r[1], line):\n            out + [st[0]] => out\n    return out\n\ndef not_taken(reg):\n    # the sessions where nobody has a mark\n    [] => out\n    for k in [0:held(reg) - 1]:\n        True => none\n        for st in reg:\n            if st[2][k] != \"-\":\n                False => none\n        if none:\n            out + [k + 1] => out\n    return out\n\ndef take(reg, marks):\n    # a new session: one mark per student, or \"-\" for each when not taken\n    [] => out\n    for i in [0:len(reg) - 1]:\n        reg[i] => st\n        out + [[st[0], st[1], st[2] + [marks[i]]]] => out\n    return out\n\ndef add_student(reg, name):\n    held(reg) + 1 => j\n    [] => marks\n    for k in [1:held(reg)]:\n        marks + [\"-\"] => marks\n    return reg + [[name, j, marks]]\n\ndef correct(reg, i, k, mark):\n    # the student i's mark for session k (1-based)\n    [] => out\n    for x in [0:len(reg) - 1]:\n        reg[x] => st\n        if x == i:\n            [] => marks\n            for y in [0:len(st[2]) - 1]:\n                if y == k - 1:\n                    marks + [mark] => marks\n                else:\n                    marks + [st[2][y]] => marks\n            out + [[st[0], st[1], marks]] => out\n        else:\n            out + [st] => out\n    return out\n",
      "python": "def default_register():\n    return [[\"Ana\", 1, [\"P\", \"P\", \"-\", \"L\", \"P\", \"A\"]], [\"Ben\", 1, [\"P\", \"A\", \"-\", \"P\", \"E\", \"P\"]], [\"Cara\", 1, [\"P\", \"P\", \"-\", \"P\", \"P\", \"L\"]], [\"Dana\", 4, [\"-\", \"-\", \"-\", \"P\", \"P\", \"P\"]], [\"Eli\", 1, [\"A\", \"L\", \"-\", \"E\", \"E\", \"A\"]]]\n\ndef held(reg):\n    if len(reg) == 0:\n        return 0\n    return len(reg[0][2])\n\ndef div(p, q):\n    f = int(p / q)\n    while f * q > p:\n        f = f - 1\n    while (f + 1) * q <= p:\n        f = f + 1\n    return f\n\ndef counts(st):\n    c = {\"P\": 0, \"L\": 0, \"E\": 0, \"A\": 0, \"-\": 0}\n    for m in st[2]:\n        c[m] = c[m] + 1\n    return c\n\ndef policies():\n    return [\"all held\", \"since joining\", \"marked\", \"marked, not excused\"]\n\ndef rate(st, policy, n):\n    c = counts(st)\n    present = c[\"P\"] + c[\"L\"]\n    if policy == \"all held\":\n        return [present, n]\n    if policy == \"since joining\":\n        return [present, n - st[1] + 1]\n    if policy == \"marked\":\n        return [present, c[\"P\"] + c[\"L\"] + c[\"E\"] + c[\"A\"]]\n    return [present, c[\"P\"] + c[\"L\"] + c[\"A\"]]\n\ndef tenths(p, q):\n    return div(2000 * p + q, 2 * q)\n\ndef percent_text(p, q):\n    if q <= 0:\n        return \"-\"\n    t = tenths(p, q)\n    return str(div(t, 10)) + \".\" + str(t - 10 * div(t, 10)) + \"%\"\n\ndef below(p, q, line):\n    return q > 0 and 100 * p < line * q\n\ndef flagged(reg, policy, line):\n    n = held(reg)\n    out = []\n    for st in reg:\n        r = rate(st, policy, n)\n        if below(r[0], r[1], line):\n            out = out + [st[0]]\n    return out\n\ndef not_taken(reg):\n    out = []\n    for k in range(0, held(reg)):\n        none = True\n        for st in reg:\n            if st[2][k] != \"-\":\n                none = False\n        if none:\n            out = out + [k + 1]\n    return out\n\ndef take(reg, marks):\n    out = []\n    for i in range(0, len(reg)):\n        st = reg[i]\n        out = out + [[st[0], st[1], st[2] + [marks[i]]]]\n    return out\n\ndef add_student(reg, name):\n    j = held(reg) + 1\n    marks = []\n    for k in range(1, held(reg)+1):\n        marks = marks + [\"-\"]\n    return reg + [[name, j, marks]]\n\ndef correct(reg, i, k, mark):\n    out = []\n    for x in range(0, len(reg)):\n        st = reg[x]\n        if x == i:\n            marks = []\n            for y in range(0, len(st[2])):\n                if y == k - 1:\n                    marks = marks + [mark]\n                else:\n                    marks = marks + [st[2][y]]\n            out = out + [[st[0], st[1], marks]]\n        else:\n            out = out + [st]\n    return out\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n3\n\n3\nP P\n3\nP X P P P\n3\nPP P P P P\n4\n\n4\nZed\n4\nAna\n\n4\nAna\n0\n4\nAna\n9\n4\nAna\nx\n4\nDana\n2\n4\nAna\n1\n\n4\nAna\n1\nQ\n4\nAna\n1\nPP\n5\n\n5\nAna\n5\nJo-Jo\n5\nBartholomews\n5\nGia\n5\nHal\n5\nIvy\n5\nJon\n3\nskip\n1\n2\n6\n",
      "screen": "== Attendance register ==\nA mark for each student at each session: present, late, excused or absent - or no mark, when the register was not taken or the student had not joined.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 0\nPick a number from 1 to 6.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> x\nPick a number from 1 to 6.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 3\nSession 7: a mark for each of Ana, Ben, Cara, Dana and Eli in turn (P, L, E or A), or skip if the register was not taken.\nmarks> \nCancelled.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 3\nSession 7: a mark for each of Ana, Ben, Cara, Dana and Eli in turn (P, L, E or A), or skip if the register was not taken.\nmarks> P P\nType 5 marks, one for each student.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 3\nSession 7: a mark for each of Ana, Ben, Cara, Dana and Eli in turn (P, L, E or A), or skip if the register was not taken.\nmarks> P X P P P\nEach mark is P, L, E or A, separated by spaces.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 3\nSession 7: a mark for each of Ana, Ben, Cara, Dana and Eli in turn (P, L, E or A), or skip if the register was not taken.\nmarks> PP P P P P\nEach mark is P, L, E or A, separated by spaces.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 4\nstudent> \nCancelled.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 4\nstudent> Zed\nNo such student.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 4\nstudent> Ana\nsession (1 to 6)> \nCancelled.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 4\nstudent> Ana\nsession (1 to 6)> 0\nType a session from 1 to 6.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 4\nstudent> Ana\nsession (1 to 6)> 9\nType a session from 1 to 6.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 4\nstudent> Ana\nsession (1 to 6)> x\nType a session from 1 to 6.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 4\nstudent> Dana\nsession (1 to 6)> 2\nDana joined at session 4.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 4\nstudent> Ana\nsession (1 to 6)> 1\nmark (P, L, E, A or none)> \nCancelled.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 4\nstudent> Ana\nsession (1 to 6)> 1\nmark (P, L, E, A or none)> Q\nType P, L, E, A or none.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 4\nstudent> Ana\nsession (1 to 6)> 1\nmark (P, L, E, A or none)> PP\nType P, L, E, A or none.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 5\nname (up to 10 letters)> \nCancelled.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 5\nname (up to 10 letters)> Ana\nAna is already on the register.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 5\nname (up to 10 letters)> Jo-Jo\nType a name of up to 10 letters.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 5\nname (up to 10 letters)> Bartholomews\nType a name of up to 10 letters.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 5\nname (up to 10 letters)> Gia\nGia joins from session 7.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 5\nname (up to 10 letters)> Hal\nHal joins from session 7.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 5\nname (up to 10 letters)> Ivy\nIvy joins from session 7.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 5\nname (up to 10 letters)> Jon\n8 students is the most.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 3\nSession 7: a mark for each of Ana, Ben, Cara, Dana, Eli, Gia, Hal and Ivy in turn (P, L, E or A), or skip if the register was not taken.\nmarks> skip\nSession 7 recorded.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 1\nRegister (P present, L late, E excused, A absent, - no mark):\n  session     1  2  3  4  5  6  7\n  Ana         P  P  -  L  P  A  -\n  Ben         P  A  -  P  E  P  -\n  Cara        P  P  -  P  P  L  -\n  Dana        -  -  -  P  P  P  -   joined at session 4\n  Eli         A  L  -  E  E  A  -\n  Gia         -  -  -  -  -  -  -   joined at session 7\n  Hal         -  -  -  -  -  -  -   joined at session 7\n  Ivy         -  -  -  -  -  -  -   joined at session 7\nSessions 3 and 7: the register was not taken.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 2\nPresent or late, out of: (1) every session held, (2) the sessions since joining, (3) the sessions with a mark, (4) those without excused ones.\n  student          (1)       (2)       (3)       (4)\n  Ana            57.1%     57.1%     80.0%     80.0%\n  Ben            42.9%     42.9%     60.0%     75.0%\n  Cara           71.4%     71.4%    100.0%    100.0%\n  Dana           42.9%     75.0%    100.0%    100.0%\n  Eli            14.3%     14.3%     20.0%     33.3%\n  Gia             0.0%      0.0%         -         -\n  Hal             0.0%      0.0%         -         -\n  Ivy             0.0%      0.0%         -         -\nBelow 80%:\n  (1) Ana, Ben, Cara, Dana, Eli, Gia, Hal and Ivy\n  (2) Ana, Ben, Cara, Dana, Eli, Gia, Hal and Ivy\n  (3) Ben and Eli\n  (4) Ben and Eli\nFlagged only when a session with no mark counts as an absence: Ana, Cara, Dana, Gia, Hal and Ivy.\nNo marked session yet, so no rate: Gia, Hal and Ivy. Counting every session held gives 0.0% instead.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 6\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\n2\n5\nFinn\n2\n3\nP P A E L P\n1\n2\n4\nAna\n3\nP\n2\n4\nCara\n6\nnone\n1\n2\n6\n",
      "screen": "== Attendance register ==\nA mark for each student at each session: present, late, excused or absent - or no mark, when the register was not taken or the student had not joined.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 1\nRegister (P present, L late, E excused, A absent, - no mark):\n  session     1  2  3  4  5  6\n  Ana         P  P  -  L  P  A\n  Ben         P  A  -  P  E  P\n  Cara        P  P  -  P  P  L\n  Dana        -  -  -  P  P  P   joined at session 4\n  Eli         A  L  -  E  E  A\nSession 3: the register was not taken.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 2\nPresent or late, out of: (1) every session held, (2) the sessions since joining, (3) the sessions with a mark, (4) those without excused ones.\n  student          (1)       (2)       (3)       (4)\n  Ana            66.7%     66.7%     80.0%     80.0%\n  Ben            50.0%     50.0%     60.0%     75.0%\n  Cara           83.3%     83.3%    100.0%    100.0%\n  Dana           50.0%    100.0%    100.0%    100.0%\n  Eli            16.7%     16.7%     20.0%     33.3%\nBelow 80%:\n  (1) Ana, Ben, Dana and Eli\n  (2) Ana, Ben and Eli\n  (3) Ben and Eli\n  (4) Ben and Eli\nFlagged only when a session with no mark counts as an absence: Ana and Dana.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 5\nname (up to 10 letters)> Finn\nFinn joins from session 7.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 2\nPresent or late, out of: (1) every session held, (2) the sessions since joining, (3) the sessions with a mark, (4) those without excused ones.\n  student          (1)       (2)       (3)       (4)\n  Ana            66.7%     66.7%     80.0%     80.0%\n  Ben            50.0%     50.0%     60.0%     75.0%\n  Cara           83.3%     83.3%    100.0%    100.0%\n  Dana           50.0%    100.0%    100.0%    100.0%\n  Eli            16.7%     16.7%     20.0%     33.3%\n  Finn            0.0%         -         -         -\nBelow 80%:\n  (1) Ana, Ben, Dana, Eli and Finn\n  (2) Ana, Ben and Eli\n  (3) Ben and Eli\n  (4) Ben and Eli\nFlagged only when a session with no mark counts as an absence: Ana, Dana and Finn.\nNo marked session yet, so no rate: Finn. Counting every session held gives 0.0% instead.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 3\nSession 7: a mark for each of Ana, Ben, Cara, Dana, Eli and Finn in turn (P, L, E or A), or skip if the register was not taken.\nmarks> P P A E L P\nSession 7 recorded.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 1\nRegister (P present, L late, E excused, A absent, - no mark):\n  session     1  2  3  4  5  6  7\n  Ana         P  P  -  L  P  A  P\n  Ben         P  A  -  P  E  P  P\n  Cara        P  P  -  P  P  L  A\n  Dana        -  -  -  P  P  P  E   joined at session 4\n  Eli         A  L  -  E  E  A  L\n  Finn        -  -  -  -  -  -  P   joined at session 7\nSession 3: the register was not taken.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 2\nPresent or late, out of: (1) every session held, (2) the sessions since joining, (3) the sessions with a mark, (4) those without excused ones.\n  student          (1)       (2)       (3)       (4)\n  Ana            71.4%     71.4%     83.3%     83.3%\n  Ben            57.1%     57.1%     66.7%     80.0%\n  Cara           71.4%     71.4%     83.3%     83.3%\n  Dana           42.9%     75.0%     75.0%    100.0%\n  Eli            28.6%     28.6%     33.3%     50.0%\n  Finn           14.3%    100.0%    100.0%    100.0%\nBelow 80%:\n  (1) Ana, Ben, Cara, Dana, Eli and Finn\n  (2) Ana, Ben, Cara, Dana and Eli\n  (3) Ben, Dana and Eli\n  (4) Eli\nFlagged only when a session with no mark counts as an absence: Ana, Cara and Finn.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 4\nstudent> Ana\nsession (1 to 7)> 3\nmark (P, L, E, A or none)> P\nAna, session 3: - -> P.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 2\nPresent or late, out of: (1) every session held, (2) the sessions since joining, (3) the sessions with a mark, (4) those without excused ones.\n  student          (1)       (2)       (3)       (4)\n  Ana            85.7%     85.7%     85.7%     85.7%\n  Ben            57.1%     57.1%     66.7%     80.0%\n  Cara           71.4%     71.4%     83.3%     83.3%\n  Dana           42.9%     75.0%     75.0%    100.0%\n  Eli            28.6%     28.6%     33.3%     50.0%\n  Finn           14.3%    100.0%    100.0%    100.0%\nBelow 80%:\n  (1) Ben, Cara, Dana, Eli and Finn\n  (2) Ben, Cara, Dana and Eli\n  (3) Ben, Dana and Eli\n  (4) Eli\nFlagged only when a session with no mark counts as an absence: Cara and Finn.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 4\nstudent> Cara\nsession (1 to 7)> 6\nmark (P, L, E, A or none)> none\nCara, session 6: L -> -.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 1\nRegister (P present, L late, E excused, A absent, - no mark):\n  session     1  2  3  4  5  6  7\n  Ana         P  P  P  L  P  A  P\n  Ben         P  A  -  P  E  P  P\n  Cara        P  P  -  P  P  -  A\n  Dana        -  -  -  P  P  P  E   joined at session 4\n  Eli         A  L  -  E  E  A  L\n  Finn        -  -  -  -  -  -  P   joined at session 7\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 2\nPresent or late, out of: (1) every session held, (2) the sessions since joining, (3) the sessions with a mark, (4) those without excused ones.\n  student          (1)       (2)       (3)       (4)\n  Ana            85.7%     85.7%     85.7%     85.7%\n  Ben            57.1%     57.1%     66.7%     80.0%\n  Cara           57.1%     57.1%     80.0%     80.0%\n  Dana           42.9%     75.0%     75.0%    100.0%\n  Eli            28.6%     28.6%     33.3%     50.0%\n  Finn           14.3%    100.0%    100.0%    100.0%\nBelow 80%:\n  (1) Ben, Cara, Dana, Eli and Finn\n  (2) Ben, Cara, Dana and Eli\n  (3) Ben, Dana and Eli\n  (4) Eli\nFlagged only when a session with no mark counts as an absence: Cara and Finn.\n\n1) register  2) rates  3) take the register  4) correct a mark  5) add a student  6) quit\nchoice> 6\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "attendance-register",
      "caseId": "156-attendance-register",
      "title": "Updating a key does not move it"
    },
    {
      "slug": "absent-vs-empty-vs-zero",
      "caseId": "230-absent-vs-empty-vs-zero",
      "title": "Three kinds of nothing, one falsy value"
    }
  ],
  "updated": "2026-10-17"
}
