{
  "id": "P039",
  "slug": "task-planner",
  "key": "P039-task-planner",
  "title": "Task planner",
  "summary": "Tasks with a length in days and the tasks each one needs first: an order that respects every dependency (Kahn's algorithm), each task's earliest start, finish and slack, a small chart, and the critical path that decides the total. A dependency that closes a cycle is named at once, and no plan is made until the cycle is broken.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P039 - Task planner\n\nUp to 15 tasks, each with a length in days and the tasks it needs finished\nfirst. The plan puts the tasks in an order that respects every dependency,\ngives each one its earliest start, its finish and its slack - how many days\nit can slip without delaying the end - draws them as a small chart, and\nnames the critical path, the chain of tasks that decides how long the whole\nthing takes. A dependency that closes a cycle is accepted but named at once,\nand no plan is possible until one of its links is removed.\n\n- `main.eml` - the menu, tasks and dependencies with their checks, and the\n  plan on screen\n- `plan.eml` - the order, the schedule, the critical path and the cycles\n\nHow each part works:\n\n- The order is Kahn's algorithm, as in the corpus case `topological-sort`:\n  take a task whose prerequisites are all done, again and again. When\n  several are ready, the one added first goes first, so the same tasks\n  always give the same plan. Tasks that wait, directly or through others,\n  on a cycle can never be taken; the plan names them and one cycle among\n  them.\n- The schedule is the critical path method. Going forward through the\n  order, a task starts when its last prerequisite finishes. Going back, a\n  task may finish as late as the earliest start of anything that waits for\n  it, or the end of the project if nothing does; its slack is the\n  difference. Lengthening a task by its slack leaves the total as it is,\n  and one day more makes it one day longer.\n- The critical path starts from the task that finishes last and steps back\n  through prerequisites that have no slack and finish exactly when the next\n  task starts. When there are two such chains, the other tasks with no\n  slack are listed too.\n- A new dependency closes a cycle when the task it points to already waits,\n  through some chain, for the task that gets it. The chain is found by a\n  depth-first search and shown as \"a needs b, b needs c, c needs a\".\n- The chart shows # for working days and - for slack, one character per\n  day, for plans of up to 60 days.\n\nWhat is checked: names of 1 to 12 letters, digits or hyphens, each different\nin any case, at most 15 tasks; 1 to 30 days; a dependency between two\ndifferent existing tasks, not one already there. An empty answer cancels.\n\nSessions: `sessions/basic.in` plans a small building job of eight tasks: 24\ndays, plumbing with one day of slack, the critical path through wiring. It\nthen makes framing need wiring - a cycle, named at once - shows that no plan\nis possible, removes that dependency, and adds a four-day tiles task after\nplumbing, which uses up plumbing's slack and makes a second critical chain.\n`sessions/bad-input.in` gives menu choices 0 and x, the lists and the plan\nwith no tasks, names with a space, of 13 characters and with !, days 0, 31\nand x, a name used twice in another case, a task that needs itself, a\ndependency given twice, a three-task cycle and the plan it blocks, the\nremoval of dependencies that are not there, and fifteen tasks with a\nsixteenth refused; the last plan has twelve tasks with seven days of slack\neach.\n\nBuilt on the verified corpus case `topological-sort` (Kahn's algorithm,\nwhich also shows a cycle when tasks are left over).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P039 task planner: tasks with a length in days and the tasks each one\n# needs finished first. The plan puts them in an order that respects every\n# dependency, gives each its earliest start, its finish and its slack, and\n# names the critical path - the chain that decides how long the whole thing\n# takes. A dependency that closes a cycle is accepted but named at once, and\n# no plan is possible until the cycle is broken.\nimport plan\n\n15 => most_tasks\n30 => longest_task\n60 => widest_chart\n\ndef trim(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 lower(s):\n    \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\" => big\n    \"abcdefghijklmnopqrstuvwxyz\" => small\n    \"\" => out\n    for c in s:\n        0 => k\n        while k < 26 and big[k] != c:\n            k + 1 => k\n        if k < 26:\n            out + small[k] => out\n        else:\n            out + c => out\n    return out\n\ndef is_name(s):\n    if s == \"\" or len(s) > 12:\n        return False\n    for c in s:\n        if not (c in \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-\"):\n            return False\n    return True\n\ndef number(s):\n    if s == \"\" or len(s) > 3:\n        return -1\n    0 => n\n    for c in s:\n        if not (c in \"0123456789\"):\n            return -1\n        n * 10 + int(c) => n\n    return n\n\ndef days_text(d):\n    if d == 1:\n        return \"1 day\"\n    return str(d) + \" days\"\n\ndef contains(xs, x):\n    for y in xs:\n        if y == x:\n            return True\n    return False\n\ndef find(names, s):\n    for i in [0:len(names) - 1]:\n        if lower(names[i]) == lower(s):\n            return i\n    return -1\n\ndef ask_task(names, prompt):\n    # The number of an existing task, or -1 when the answer is empty.\n    while True:\n        trim(input(prompt)) => answer\n        if answer == \"\":\n            return -1\n        find(names, answer) => t\n        if t >= 0:\n            return t\n        (\"There is no task called \" + answer + \".\") ^0\n\ndef chain_text(names, chain):\n    # [a, b, c, a] as \"a needs b, b needs c, c needs a\".\n    \"\" => out\n    for i in [0:len(chain) - 2]:\n        if out != \"\":\n            out + \", \" => out\n        out + names[chain[i]] + \" needs \" + names[chain[i + 1]] => out\n    return out\n\ndef listed(names, tasks):\n    \"\" => out\n    for i in [0:len(tasks) - 1]:\n        if i > 0 and i == len(tasks) - 1:\n            out + \" and \" => out\n        elif i > 0:\n            out + \", \" => out\n        out + names[tasks[i]] => out\n    return out\n\ndef add_task(state):\n    state[0] => names\n    if len(names) == most_tasks:\n        (\"That makes \" + str(most_tasks) + \" tasks, the most.\") ^0\n        return state\n    while True:\n        trim(input(\"task name> \")) => name\n        if name == \"\":\n            \"Cancelled.\" ^0\n            return state\n        if not is_name(name):\n            \"A name is 1 to 12 letters, digits or hyphens.\" ^0\n        elif find(names, name) >= 0:\n            (\"There is already a task called \" + names[find(names, name)] + \".\") ^0\n        else:\n            while True:\n                trim(input(\"days (1 to \" + str(longest_task) + \")> \")) => answer\n                if answer == \"\":\n                    \"Cancelled.\" ^0\n                    return state\n                number(answer) => d\n                if d >= 1 and d <= longest_task:\n                    (name + \" added: \" + days_text(d) + \".\") ^0\n                    return [names + [name], state[1] + [d], state[2] + [[]]]\n                (\"Type a number from 1 to \" + str(longest_task) + \".\") ^0\n\ndef add_need(state):\n    state[0] => names\n    state[2] => needs\n    if len(names) < 2:\n        \"Add at least two tasks first.\" ^0\n        return state\n    ask_task(names, \"task> \") => t\n    if t == -1:\n        \"Cancelled.\" ^0\n        return state\n    while True:\n        ask_task(names, \"needs which task first> \") => p\n        if p == -1:\n            \"Cancelled.\" ^0\n            return state\n        if p == t:\n            \"A task cannot need itself.\" ^0\n        elif contains(needs[t], p):\n            (names[t] + \" already needs \" + names[p] + \".\") ^0\n            return state\n        else:\n            needs[0:t] + [needs[t] + [p]] + needs[t + 1:len(needs)] => needs\n            (names[t] + \" now needs \" + names[p] + \" first.\") ^0\n            plan.cycle_through(needs, t, p) => c\n            if len(c) > 0:\n                (\"That closes a cycle: \" + chain_text(names, c) + \". No plan is possible until one of these is removed.\") ^0\n            return [names, state[1], needs]\n\ndef drop_need(state):\n    state[0] => names\n    state[2] => needs\n    ask_task(names, \"task> \") => t\n    if t == -1:\n        \"Cancelled.\" ^0\n        return state\n    if len(needs[t]) == 0:\n        (names[t] + \" needs nothing.\") ^0\n        return state\n    (\"It needs \" + listed(names, needs[t]) + \".\") ^0\n    while True:\n        ask_task(names, \"no longer needs> \") => p\n        if p == -1:\n            \"Cancelled.\" ^0\n            return state\n        if contains(needs[t], p):\n            [] => kept\n            for q in needs[t]:\n                if q != p:\n                    kept + [q] => kept\n            (names[t] + \" no longer needs \" + names[p] + \".\") ^0\n            return [names, state[1], needs[0:t] + [kept] + needs[t + 1:len(needs)]]\n        (names[t] + \" does not need \" + names[p] + \".\") ^0\n\ndef pad(s, width):\n    while len(s) < width:\n        s + \" \" => s\n    return s\n\ndef right(s, width):\n    while len(s) < width:\n        \" \" + s => s\n    return s\n\ndef show_tasks(state):\n    state[0] => names\n    if len(names) == 0:\n        \"No tasks yet.\" ^0\n        return 0\n    for t in [0:len(names) - 1]:\n        \"\" => after\n        if len(state[2][t]) > 0:\n            \", needs \" + listed(names, state[2][t]) => after\n        (\"  \" + names[t] + \" (\" + days_text(state[1][t]) + \")\" + after) ^0\n    return 0\n\ndef show_plan(state):\n    state[0] => names\n    state[1] => days\n    state[2] => needs\n    if len(names) == 0:\n        \"No tasks yet.\" ^0\n        return 0\n    plan.order(needs) => seq\n    if len(seq) < len(names):\n        [] => stuck\n        for t in [0:len(names) - 1]:\n            if not contains(seq, t):\n                stuck + [t] => stuck\n        (\"No plan: \" + listed(names, stuck) + \" can never start, because of a cycle - \" + chain_text(names, plan.some_cycle(needs, stuck)) + \".\") ^0\n        return 0\n    plan.schedule(needs, days, seq) => sch\n    sch[3] => total\n    (str(len(names)) + \" tasks, \" + days_text(total) + \" in all. Start and finish count days from the beginning.\") ^0\n    \"  task          days  start  finish  slack\" => header\n    if total <= widest_chart:\n        header + \"   \" + \"#\" + \" = working, - = slack\" => header\n    header ^0\n    for t in seq:\n        (\"  \" + pad(names[t], 12) + right(str(days[t]), 6) + right(str(sch[0][t]), 7) + right(str(sch[1][t]), 8) + right(str(sch[2][t]), 7)) => line\n        if total <= widest_chart:\n            line + \"   \" + \" \" * sch[0][t] + \"#\" * days[t] + \"-\" * sch[2][t] => line\n        line ^0\n    plan.critical_path(needs, seq, sch) => cp\n    \"\" => chain\n    for t in cp:\n        if chain != \"\":\n            chain + \" -> \" => chain\n        chain + names[t] => chain\n    (\"Critical path: \" + chain + \" (\" + days_text(total) + \"). A delay to any of these delays the end.\") ^0\n    [] => others\n    for t in seq:\n        if sch[2][t] == 0 and not contains(cp, t):\n            others + [t] => others\n    if len(others) > 0:\n        (\"Also with no slack: \" + listed(names, others) + \".\") ^0\n    return 0\n\n\"== Task planner ==\" ^0\n\"Tasks with their length in days, and the tasks each one needs finished first.\" ^0\n[[], [], []] => state\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\" ^0\n    trim(input(\"choice> \")) => choice\n    if choice == \"1\":\n        add_task(state) => state\n    elif choice == \"2\":\n        add_need(state) => state\n    elif choice == \"3\":\n        drop_need(state) => state\n    elif choice == \"4\":\n        show_tasks(state)\n    elif choice == \"5\":\n        show_plan(state)\n    elif choice == \"6\":\n        False => running\n    else:\n        \"Pick a number from 1 to 6.\" ^0\n\"Bye.\" ^0\n",
      "python": "import plan\nmost_tasks = 15\nlongest_task = 30\nwidest_chart = 60\n\ndef trim(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 lower(s):\n    big = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n    small = \"abcdefghijklmnopqrstuvwxyz\"\n    out = \"\"\n    for c in s:\n        k = 0\n        while k < 26 and big[k] != c:\n            k = k + 1\n        if k < 26:\n            out = out + small[k]\n        else:\n            out = out + c\n    return out\n\ndef is_name(s):\n    if s == \"\" or len(s) > 12:\n        return False\n    for c in s:\n        if not c in \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-\":\n            return False\n    return True\n\ndef number(s):\n    if s == \"\" or len(s) > 3:\n        return -1\n    n = 0\n    for c in s:\n        if not c in \"0123456789\":\n            return -1\n        n = n * 10 + int(c)\n    return n\n\ndef days_text(d):\n    if d == 1:\n        return \"1 day\"\n    return str(d) + \" days\"\n\ndef contains(xs, x):\n    for y in xs:\n        if y == x:\n            return True\n    return False\n\ndef find(names, s):\n    for i in range(0, len(names)):\n        if lower(names[i]) == lower(s):\n            return i\n    return -1\n\ndef ask_task(names, prompt):\n    while True:\n        answer = trim(input(prompt))\n        if answer == \"\":\n            return -1\n        t = find(names, answer)\n        if t >= 0:\n            return t\n        print(\"There is no task called \" + answer + \".\")\n\ndef chain_text(names, chain):\n    out = \"\"\n    for i in range(0, len(chain) - 2+1):\n        if out != \"\":\n            out = out + \", \"\n        out = out + names[chain[i]] + \" needs \" + names[chain[i + 1]]\n    return out\n\ndef listed(names, tasks):\n    out = \"\"\n    for i in range(0, len(tasks)):\n        if i > 0 and i == len(tasks) - 1:\n            out = out + \" and \"\n        elif i > 0:\n            out = out + \", \"\n        out = out + names[tasks[i]]\n    return out\n\ndef add_task(state):\n    names = state[0]\n    if len(names) == most_tasks:\n        print(\"That makes \" + str(most_tasks) + \" tasks, the most.\")\n        return state\n    while True:\n        name = trim(input(\"task name> \"))\n        if name == \"\":\n            print(\"Cancelled.\")\n            return state\n        if not is_name(name):\n            print(\"A name is 1 to 12 letters, digits or hyphens.\")\n        elif find(names, name) >= 0:\n            print(\"There is already a task called \" + names[find(names, name)] + \".\")\n        else:\n            while True:\n                answer = trim(input(\"days (1 to \" + str(longest_task) + \")> \"))\n                if answer == \"\":\n                    print(\"Cancelled.\")\n                    return state\n                d = number(answer)\n                if d >= 1 and d <= longest_task:\n                    print(name + \" added: \" + days_text(d) + \".\")\n                    return [names + [name], state[1] + [d], state[2] + [[]]]\n                print(\"Type a number from 1 to \" + str(longest_task) + \".\")\n\ndef add_need(state):\n    names = state[0]\n    needs = state[2]\n    if len(names) < 2:\n        print(\"Add at least two tasks first.\")\n        return state\n    t = ask_task(names, \"task> \")\n    if t == -1:\n        print(\"Cancelled.\")\n        return state\n    while True:\n        p = ask_task(names, \"needs which task first> \")\n        if p == -1:\n            print(\"Cancelled.\")\n            return state\n        if p == t:\n            print(\"A task cannot need itself.\")\n        elif contains(needs[t], p):\n            print(names[t] + \" already needs \" + names[p] + \".\")\n            return state\n        else:\n            needs = needs[0:t] + [needs[t] + [p]] + needs[t + 1:len(needs)]\n            print(names[t] + \" now needs \" + names[p] + \" first.\")\n            c = plan.cycle_through(needs, t, p)\n            if len(c) > 0:\n                print(\"That closes a cycle: \" + chain_text(names, c) + \". No plan is possible until one of these is removed.\")\n            return [names, state[1], needs]\n\ndef drop_need(state):\n    names = state[0]\n    needs = state[2]\n    t = ask_task(names, \"task> \")\n    if t == -1:\n        print(\"Cancelled.\")\n        return state\n    if len(needs[t]) == 0:\n        print(names[t] + \" needs nothing.\")\n        return state\n    print(\"It needs \" + listed(names, needs[t]) + \".\")\n    while True:\n        p = ask_task(names, \"no longer needs> \")\n        if p == -1:\n            print(\"Cancelled.\")\n            return state\n        if contains(needs[t], p):\n            kept = []\n            for q in needs[t]:\n                if q != p:\n                    kept = kept + [q]\n            print(names[t] + \" no longer needs \" + names[p] + \".\")\n            return [names, state[1], needs[0:t] + [kept] + needs[t + 1:len(needs)]]\n        print(names[t] + \" does not need \" + names[p] + \".\")\n\ndef pad(s, width):\n    while len(s) < width:\n        s = s + \" \"\n    return s\n\ndef right(s, width):\n    while len(s) < width:\n        s = \" \" + s\n    return s\n\ndef show_tasks(state):\n    names = state[0]\n    if len(names) == 0:\n        print(\"No tasks yet.\")\n        return 0\n    for t in range(0, len(names)):\n        after = \"\"\n        if len(state[2][t]) > 0:\n            after = \", needs \" + listed(names, state[2][t])\n        print(\"  \" + names[t] + \" (\" + days_text(state[1][t]) + \")\" + after)\n    return 0\n\ndef show_plan(state):\n    names = state[0]\n    days = state[1]\n    needs = state[2]\n    if len(names) == 0:\n        print(\"No tasks yet.\")\n        return 0\n    seq = plan.order(needs)\n    if len(seq) < len(names):\n        stuck = []\n        for t in range(0, len(names)):\n            if not contains(seq, t):\n                stuck = stuck + [t]\n        print(\"No plan: \" + listed(names, stuck) + \" can never start, because of a cycle - \" + chain_text(names, plan.some_cycle(needs, stuck)) + \".\")\n        return 0\n    sch = plan.schedule(needs, days, seq)\n    total = sch[3]\n    print(str(len(names)) + \" tasks, \" + days_text(total) + \" in all. Start and finish count days from the beginning.\")\n    header = \"  task          days  start  finish  slack\"\n    if total <= widest_chart:\n        header = header + \"   \" + \"#\" + \" = working, - = slack\"\n    print(header)\n    for t in seq:\n        line = \"  \" + pad(names[t], 12) + right(str(days[t]), 6) + right(str(sch[0][t]), 7) + right(str(sch[1][t]), 8) + right(str(sch[2][t]), 7)\n        if total <= widest_chart:\n            line = line + \"   \" + \" \" * sch[0][t] + \"#\" * days[t] + \"-\" * sch[2][t]\n        print(line)\n    cp = plan.critical_path(needs, seq, sch)\n    chain = \"\"\n    for t in cp:\n        if chain != \"\":\n            chain = chain + \" -> \"\n        chain = chain + names[t]\n    print(\"Critical path: \" + chain + \" (\" + days_text(total) + \"). A delay to any of these delays the end.\")\n    others = []\n    for t in seq:\n        if sch[2][t] == 0 and not contains(cp, t):\n            others = others + [t]\n    if len(others) > 0:\n        print(\"Also with no slack: \" + listed(names, others) + \".\")\n    return 0\n\nprint(\"== Task planner ==\")\nprint(\"Tasks with their length in days, and the tasks each one needs finished first.\")\nstate = [[], [], []]\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\")\n    choice = trim(input(\"choice> \"))\n    if choice == \"1\":\n        state = add_task(state)\n    elif choice == \"2\":\n        state = add_need(state)\n    elif choice == \"3\":\n        state = drop_need(state)\n    elif choice == \"4\":\n        show_tasks(state)\n    elif choice == \"5\":\n        show_plan(state)\n    elif choice == \"6\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 6.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "plan.eml",
      "eml": "# P039 task planner - the order and the schedule. Tasks are numbered in the\n# order they were added; needs[t] lists the tasks t waits for.\n\ndef order(needs):\n    # Kahn's algorithm, as in the corpus case topological-sort: repeatedly\n    # take a task whose prerequisites are all done. When several are ready,\n    # the one added first goes first, so the plan never depends on chance.\n    # Returns the tasks in order; if a cycle blocks some, they are missing.\n    len(needs) => n\n    [False] * n => done\n    [] => out\n    True => moving\n    while moving:\n        -1 => pick\n        for t in [0:n - 1]:\n            if pick == -1 and not done[t]:\n                True => ready\n                for p in needs[t]:\n                    if not done[p]:\n                        False => ready\n                if ready:\n                    t => pick\n        if pick == -1:\n            False => moving\n        else:\n            True => done[pick]\n            out + [pick] => out\n    return out\n\ndef path(needs, start, goal):\n    # A chain of \"waits for\" from start to goal, or [] if there is none\n    # (depth-first, prerequisites in the order they were given).\n    [[start]] => stack\n    [False] * len(needs) => seen\n    while len(stack) > 0:\n        stack[len(stack) - 1] => chain\n        stack[0:len(stack) - 1] => stack\n        chain[len(chain) - 1] => t\n        if t == goal and len(chain) > 1:\n            return chain\n        if not seen[t]:\n            True => seen[t]\n            len(needs[t]) - 1 => i\n            while i >= 0:\n                stack + [chain + [needs[t][i]]] => stack\n                i - 1 => i\n    return []\n\ndef cycle_through(needs, t, p):\n    # If t now waits for p and p already waits (perhaps through others)\n    # for t, the cycle t -> p -> ... -> t; otherwise [].\n    path(needs, p, t) => back\n    if len(back) == 0:\n        return []\n    return [t] + back\n\ndef some_cycle(needs, stuck):\n    # One cycle among the tasks a plan could not place: follow the first\n    # stuck prerequisite until a task comes round again.\n    [False] * len(needs) => is_stuck\n    for t in stuck:\n        True => is_stuck[t]\n    [stuck[0]] => walk\n    while True:\n        walk[len(walk) - 1] => t\n        -1 => nxt\n        for p in needs[t]:\n            if nxt == -1 and is_stuck[p]:\n                p => nxt\n        for i in [0:len(walk) - 1]:\n            if walk[i] == nxt:\n                return walk[i:len(walk)] + [nxt]\n        walk + [nxt] => walk\n\ndef schedule(needs, days, seq):\n    # The critical path method over a valid order: a task starts when its\n    # last prerequisite finishes (forward pass), and may finish as late as\n    # the earliest start of what waits for it without delaying the end\n    # (backward pass). Returns [start, finish, slack, total].\n    len(needs) => n\n    [0] * n => start\n    [0] * n => finish\n    for t in seq:\n        0 => s\n        for p in needs[t]:\n            if finish[p] > s:\n                finish[p] => s\n        s => start[t]\n        s + days[t] => finish[t]\n    0 => total\n    for t in [0:n - 1]:\n        if finish[t] > total:\n            finish[t] => total\n    [total] * n => latest\n    len(seq) - 1 => i\n    while i >= 0:\n        seq[i] => t\n        for u in [0:n - 1]:\n            for p in needs[u]:\n                if p == t and latest[u] - days[u] < latest[t]:\n                    latest[u] - days[u] => latest[t]\n        i - 1 => i\n    [] => slack\n    for t in [0:n - 1]:\n        slack + [latest[t] - finish[t]] => slack\n    return [start, finish, slack, total]\n\ndef critical_path(needs, seq, sch):\n    # A chain of tasks with no slack from the first to the end: start at the\n    # task finishing last and step back to a prerequisite that finishes\n    # exactly when it starts.\n    sch[0] => start\n    sch[1] => finish\n    sch[2] => slack\n    -1 => t\n    for u in seq:\n        if slack[u] == 0 and finish[u] == sch[3] and t == -1:\n            u => t\n    [t] => chain\n    True => going\n    while going:\n        -1 => back\n        for p in needs[t]:\n            if back == -1 and finish[p] == start[t] and slack[p] == 0:\n                p => back\n        if back == -1:\n            False => going\n        else:\n            [back] + chain => chain\n            back => t\n    return chain\n",
      "python": "def order(needs):\n    n = len(needs)\n    done = [False] * n\n    out = []\n    moving = True\n    while moving:\n        pick = -1\n        for t in range(0, n):\n            if pick == -1 and not done[t]:\n                ready = True\n                for p in needs[t]:\n                    if not done[p]:\n                        ready = False\n                if ready:\n                    pick = t\n        if pick == -1:\n            moving = False\n        else:\n            done[pick] = True\n            out = out + [pick]\n    return out\n\ndef path(needs, start, goal):\n    stack = [[start]]\n    seen = [False] * len(needs)\n    while len(stack) > 0:\n        chain = stack[len(stack) - 1]\n        stack = stack[0:len(stack) - 1]\n        t = chain[len(chain) - 1]\n        if t == goal and len(chain) > 1:\n            return chain\n        if not seen[t]:\n            seen[t] = True\n            i = len(needs[t]) - 1\n            while i >= 0:\n                stack = stack + [chain + [needs[t][i]]]\n                i = i - 1\n    return []\n\ndef cycle_through(needs, t, p):\n    back = path(needs, p, t)\n    if len(back) == 0:\n        return []\n    return [t] + back\n\ndef some_cycle(needs, stuck):\n    is_stuck = [False] * len(needs)\n    for t in stuck:\n        is_stuck[t] = True\n    walk = [stuck[0]]\n    while True:\n        t = walk[len(walk) - 1]\n        nxt = -1\n        for p in needs[t]:\n            if nxt == -1 and is_stuck[p]:\n                nxt = p\n        for i in range(0, len(walk)):\n            if walk[i] == nxt:\n                return walk[i:len(walk)] + [nxt]\n        walk = walk + [nxt]\n\ndef schedule(needs, days, seq):\n    n = len(needs)\n    start = [0] * n\n    finish = [0] * n\n    for t in seq:\n        s = 0\n        for p in needs[t]:\n            if finish[p] > s:\n                s = finish[p]\n        start[t] = s\n        finish[t] = s + days[t]\n    total = 0\n    for t in range(0, n):\n        if finish[t] > total:\n            total = finish[t]\n    latest = [total] * n\n    i = len(seq) - 1\n    while i >= 0:\n        t = seq[i]\n        for u in range(0, n):\n            for p in needs[u]:\n                if p == t and latest[u] - days[u] < latest[t]:\n                    latest[t] = latest[u] - days[u]\n        i = i - 1\n    slack = []\n    for t in range(0, n):\n        slack = slack + [latest[t] - finish[t]]\n    return [start, finish, slack, total]\n\ndef critical_path(needs, seq, sch):\n    start = sch[0]\n    finish = sch[1]\n    slack = sch[2]\n    t = -1\n    for u in seq:\n        if slack[u] == 0 and finish[u] == sch[3] and t == -1:\n            t = u\n    chain = [t]\n    going = True\n    while going:\n        back = -1\n        for p in needs[t]:\n            if back == -1 and finish[p] == start[t] and slack[p] == 0:\n                back = p\n        if back == -1:\n            going = False\n        else:\n            chain = [back] + chain\n            t = back\n    return chain\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n4\n5\n3\n\n2\n1\na b\nthirteen-char\nbad!\nWrite\n0\n31\nx\n\n1\nWrite\n5\n1\nwrite\n\n2\n1\nTest\n2\n1\nShip\n1\n2\nTset\nTest\nTest\nWrite\n2\nTest\nWrite\n2\nShip\nTest\n2\nWrite\nShip\n5\n3\nWrite\n3\nWrite\nTest\nShip\n3\nWrite\n1\ntask-4\n1\n1\ntask-5\n1\n1\ntask-6\n1\n1\ntask-7\n1\n1\ntask-8\n1\n1\ntask-9\n1\n1\ntask-10\n1\n1\ntask-11\n1\n1\ntask-12\n1\n1\ntask-13\n1\n1\ntask-14\n1\n1\ntask-15\n1\n1\n4\n5\n6\n",
      "screen": "== Task planner ==\nTasks with their length in days, and the tasks each one needs finished first.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 0\nPick a number from 1 to 6.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> x\nPick a number from 1 to 6.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 4\nNo tasks yet.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 5\nNo tasks yet.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 3\ntask> \nCancelled.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 2\nAdd at least two tasks first.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 1\ntask name> a b\nA name is 1 to 12 letters, digits or hyphens.\ntask name> thirteen-char\nA name is 1 to 12 letters, digits or hyphens.\ntask name> bad!\nA name is 1 to 12 letters, digits or hyphens.\ntask name> Write\ndays (1 to 30)> 0\nType a number from 1 to 30.\ndays (1 to 30)> 31\nType a number from 1 to 30.\ndays (1 to 30)> x\nType a number from 1 to 30.\ndays (1 to 30)> \nCancelled.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 1\ntask name> Write\ndays (1 to 30)> 5\nWrite added: 5 days.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 1\ntask name> write\nThere is already a task called Write.\ntask name> \nCancelled.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 2\nAdd at least two tasks first.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 1\ntask name> Test\ndays (1 to 30)> 2\nTest added: 2 days.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 1\ntask name> Ship\ndays (1 to 30)> 1\nShip added: 1 day.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 2\ntask> Tset\nThere is no task called Tset.\ntask> Test\nneeds which task first> Test\nA task cannot need itself.\nneeds which task first> Write\nTest now needs Write first.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 2\ntask> Test\nneeds which task first> Write\nTest already needs Write.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 2\ntask> Ship\nneeds which task first> Test\nShip now needs Test first.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 2\ntask> Write\nneeds which task first> Ship\nWrite now needs Ship first.\nThat closes a cycle: Write needs Ship, Ship needs Test, Test needs Write. No plan is possible until one of these is removed.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 5\nNo plan: Write, Test and Ship can never start, because of a cycle - Write needs Ship, Ship needs Test, Test needs Write.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 3\ntask> Write\nIt needs Ship.\nno longer needs> 3\nThere is no task called 3.\nno longer needs> Write\nWrite does not need Write.\nno longer needs> Test\nWrite does not need Test.\nno longer needs> Ship\nWrite no longer needs Ship.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 3\ntask> Write\nWrite needs nothing.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 1\ntask name> task-4\ndays (1 to 30)> 1\ntask-4 added: 1 day.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 1\ntask name> task-5\ndays (1 to 30)> 1\ntask-5 added: 1 day.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 1\ntask name> task-6\ndays (1 to 30)> 1\ntask-6 added: 1 day.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 1\ntask name> task-7\ndays (1 to 30)> 1\ntask-7 added: 1 day.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 1\ntask name> task-8\ndays (1 to 30)> 1\ntask-8 added: 1 day.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 1\ntask name> task-9\ndays (1 to 30)> 1\ntask-9 added: 1 day.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 1\ntask name> task-10\ndays (1 to 30)> 1\ntask-10 added: 1 day.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 1\ntask name> task-11\ndays (1 to 30)> 1\ntask-11 added: 1 day.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 1\ntask name> task-12\ndays (1 to 30)> 1\ntask-12 added: 1 day.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 1\ntask name> task-13\ndays (1 to 30)> 1\ntask-13 added: 1 day.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 1\ntask name> task-14\ndays (1 to 30)> 1\ntask-14 added: 1 day.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 1\ntask name> task-15\ndays (1 to 30)> 1\ntask-15 added: 1 day.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 1\nThat makes 15 tasks, the most.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 4\n  Write (5 days)\n  Test (2 days), needs Write\n  Ship (1 day), needs Test\n  task-4 (1 day)\n  task-5 (1 day)\n  task-6 (1 day)\n  task-7 (1 day)\n  task-8 (1 day)\n  task-9 (1 day)\n  task-10 (1 day)\n  task-11 (1 day)\n  task-12 (1 day)\n  task-13 (1 day)\n  task-14 (1 day)\n  task-15 (1 day)\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 5\n15 tasks, 8 days in all. Start and finish count days from the beginning.\n  task          days  start  finish  slack   # = working, - = slack\n  Write            5      0       5      0   #####\n  Test             2      5       7      0        ##\n  Ship             1      7       8      0          #\n  task-4           1      0       1      7   #-------\n  task-5           1      0       1      7   #-------\n  task-6           1      0       1      7   #-------\n  task-7           1      0       1      7   #-------\n  task-8           1      0       1      7   #-------\n  task-9           1      0       1      7   #-------\n  task-10          1      0       1      7   #-------\n  task-11          1      0       1      7   #-------\n  task-12          1      0       1      7   #-------\n  task-13          1      0       1      7   #-------\n  task-14          1      0       1      7   #-------\n  task-15          1      0       1      7   #-------\nCritical path: Write -> Test -> Ship (8 days). A delay to any of these delays the end.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 6\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\ndesign\n3\n1\npermit\n5\n1\nfoundation\n4\n1\nframing\n6\n1\nwiring\n3\n1\nplumbing\n2\n1\ninspect\n1\n1\npaint\n2\n2\npermit\ndesign\n2\nfoundation\npermit\n2\nframing\nfoundation\n2\nwiring\nframing\n2\nplumbing\nframing\n2\ninspect\nwiring\n2\ninspect\nplumbing\n2\npaint\ninspect\n4\n5\n2\nframing\nwiring\n5\n3\nframing\nwiring\n5\n1\ntiles\n4\n2\ntiles\nplumbing\n5\n6\n",
      "screen": "== Task planner ==\nTasks with their length in days, and the tasks each one needs finished first.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 1\ntask name> design\ndays (1 to 30)> 3\ndesign added: 3 days.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 1\ntask name> permit\ndays (1 to 30)> 5\npermit added: 5 days.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 1\ntask name> foundation\ndays (1 to 30)> 4\nfoundation added: 4 days.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 1\ntask name> framing\ndays (1 to 30)> 6\nframing added: 6 days.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 1\ntask name> wiring\ndays (1 to 30)> 3\nwiring added: 3 days.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 1\ntask name> plumbing\ndays (1 to 30)> 2\nplumbing added: 2 days.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 1\ntask name> inspect\ndays (1 to 30)> 1\ninspect added: 1 day.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 1\ntask name> paint\ndays (1 to 30)> 2\npaint added: 2 days.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 2\ntask> permit\nneeds which task first> design\npermit now needs design first.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 2\ntask> foundation\nneeds which task first> permit\nfoundation now needs permit first.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 2\ntask> framing\nneeds which task first> foundation\nframing now needs foundation first.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 2\ntask> wiring\nneeds which task first> framing\nwiring now needs framing first.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 2\ntask> plumbing\nneeds which task first> framing\nplumbing now needs framing first.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 2\ntask> inspect\nneeds which task first> wiring\ninspect now needs wiring first.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 2\ntask> inspect\nneeds which task first> plumbing\ninspect now needs plumbing first.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 2\ntask> paint\nneeds which task first> inspect\npaint now needs inspect first.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 4\n  design (3 days)\n  permit (5 days), needs design\n  foundation (4 days), needs permit\n  framing (6 days), needs foundation\n  wiring (3 days), needs framing\n  plumbing (2 days), needs framing\n  inspect (1 day), needs wiring and plumbing\n  paint (2 days), needs inspect\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 5\n8 tasks, 24 days in all. Start and finish count days from the beginning.\n  task          days  start  finish  slack   # = working, - = slack\n  design           3      0       3      0   ###\n  permit           5      3       8      0      #####\n  foundation       4      8      12      0           ####\n  framing          6     12      18      0               ######\n  wiring           3     18      21      0                     ###\n  plumbing         2     18      20      1                     ##-\n  inspect          1     21      22      0                        #\n  paint            2     22      24      0                         ##\nCritical path: design -> permit -> foundation -> framing -> wiring -> inspect -> paint (24 days). A delay to any of these delays the end.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 2\ntask> framing\nneeds which task first> wiring\nframing now needs wiring first.\nThat closes a cycle: framing needs wiring, wiring needs framing. No plan is possible until one of these is removed.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 5\nNo plan: framing, wiring, plumbing, inspect and paint can never start, because of a cycle - framing needs wiring, wiring needs framing.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 3\ntask> framing\nIt needs foundation and wiring.\nno longer needs> wiring\nframing no longer needs wiring.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 5\n8 tasks, 24 days in all. Start and finish count days from the beginning.\n  task          days  start  finish  slack   # = working, - = slack\n  design           3      0       3      0   ###\n  permit           5      3       8      0      #####\n  foundation       4      8      12      0           ####\n  framing          6     12      18      0               ######\n  wiring           3     18      21      0                     ###\n  plumbing         2     18      20      1                     ##-\n  inspect          1     21      22      0                        #\n  paint            2     22      24      0                         ##\nCritical path: design -> permit -> foundation -> framing -> wiring -> inspect -> paint (24 days). A delay to any of these delays the end.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 1\ntask name> tiles\ndays (1 to 30)> 4\ntiles added: 4 days.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 2\ntask> tiles\nneeds which task first> plumbing\ntiles now needs plumbing first.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 5\n9 tasks, 24 days in all. Start and finish count days from the beginning.\n  task          days  start  finish  slack   # = working, - = slack\n  design           3      0       3      0   ###\n  permit           5      3       8      0      #####\n  foundation       4      8      12      0           ####\n  framing          6     12      18      0               ######\n  wiring           3     18      21      0                     ###\n  plumbing         2     18      20      0                     ##\n  inspect          1     21      22      0                        #\n  paint            2     22      24      0                         ##\n  tiles            4     20      24      0                       ####\nCritical path: design -> permit -> foundation -> framing -> wiring -> inspect -> paint (24 days). A delay to any of these delays the end.\nAlso with no slack: plumbing and tiles.\n\n1) add a task  2) add a dependency  3) remove a dependency  4) tasks  5) plan  6) quit\nchoice> 6\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "topological-sort",
      "caseId": "107-topological-sort",
      "title": "Topological sort (Kahn's algorithm)"
    }
  ],
  "updated": "2026-10-10"
}
