{
  "id": "P072",
  "slug": "table-sorter",
  "key": "P072-table-sorter",
  "title": "Table sorter",
  "summary": "A table sorted by several columns, each up or down, by one stable sort or one pass per column; a stable and an unstable sort side by side; and the comparisons selection, insertion, merge and quicksort make on random, sorted, reversed and repetitive lists.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P072 - Table sorter\n\nA table of eight people sorted by several columns, each up or down. A\nstable and an unstable sort shown side by side, sorted the same but with\nties in different orders. And the comparisons that selection, insertion,\nmerge and quicksort make on up to 200 numbers in four kinds of order.\n\n- `main.eml` - the menu, reading sort keys, and showing tables and counts\n- `sorting.eml` - the multi-column sort, sorting one column at a time,\n  the unstable sort, the four counting sorts, and the test numbers\n\nHow each part works:\n\n- Sorting by \"team, score desc\": insertion sort where a row moves left\n  only past rows it goes strictly before, deciding by the first column\n  where the two differ. Rows equal on every column keep their order, so\n  the sort is stable. Every sort works on a copy, as the corpus case\n  `sort-stability-witness` warns: without one, a sort writing through\n  its indices would change its own input.\n- One column at a time: the same order comes from one stable sort per\n  column, the LAST column first. Each pass keeps the order the earlier\n  passes made among its ties. The program checks this every time.\n- Stable or not: the same insertion sort with >= instead of > in its\n  guard still sorts, but it moves rows past equal ones too and reverses\n  every group of ties. That one character is the corpus case's point.\n- Counts: selection sort always makes n(n-1)/2 comparisons. Insertion\n  makes n - 1 on a list in order and n(n-1)/2 on one in reverse. Merge\n  sort never needs more than n log2 n. Quicksort is the corpus case\n  `quicksort-recursive`, partitioning around the last element. On a list\n  already in order (or in\n  reverse) every pivot is an end value, and it makes n(n-1)/2 comparisons,\n  as many as selection sort.\n- Test numbers: \"random\" and \"few\" (only 5 different values) come from a\n  linear congruential generator with the constants of Numerical Recipes,\n  using its top 16 bits, since the low bits of such a generator repeat\n  quickly.\n\nWhat is checked: menu choices 1 to 6; sort keys from name, team, year and\nscore, each once, each optionally followed by asc or desc, separated by\ncommas; a column name; 2 to 200 numbers; random, sorted, reversed or few.\nAn empty answer cancels.\n\nSessions: `sessions/basic.in` gives:\n- the table, then sorted by team and score down, and the same order from\n  one pass per column;\n- the stable and unstable sorts by score on that table: 6 of 8 rows in\n  different places;\n- back to the first order, then sorted by year down and name;\n- the counts for 200 numbers sorted and random, 50 reversed, and 100 with\n  few values.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- the keys \"colour\", \"team up\", \"team, team\" and \"team,\", then a cancel;\n- the column colour, then name, where nothing ties;\n- 1, 201 and abc numbers, and the order \"shuffled\", then a cancel.\n\nBuilt on the verified corpus cases `sort-stability-witness` (stable\nagainst unstable insertion sort, checked by what an order check cannot\nsee) and `quicksort-recursive` (Lomuto partition, in place).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P072 table sorter: a small table sorted by several columns, each up or\n# down; what a stable sort keeps that an unstable one loses; and how many\n# comparisons selection, insertion, merge and quicksort make.\nimport sorting\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 number(s):\n    if len(s) == 0 or len(s) > 4:\n        return -1\n    0 => n\n    for c in s:\n        if not (c >= \"0\" and c <= \"9\"):\n            return -1\n        n * 10 + int(c) => n\n    return n\n\ndef pad(s, n):\n    while len(s) < n:\n        s + \" \" => s\n    return s\n\ndef lpad(s, n):\n    while len(s) < n:\n        \" \" + s => s\n    return s\n\ndef column(name):\n    if name == \"name\":\n        return 0\n    if name == \"team\":\n        return 1\n    if name == \"year\":\n        return 2\n    if name == \"score\":\n        return 3\n    return -1\n\ndef words(s):\n    # Split on spaces.\n    [] => out\n    \"\" => w\n    for c in s + \" \":\n        if c == \" \":\n            if w != \"\":\n                out + [w] => out\n            \"\" => w\n        else:\n            w + c => w\n    return out\n\ndef keys_of(s):\n    # \"team, score desc\" -> [[1, False], [3, True]]; [] if not understood.\n    [] => out\n    0 => i\n    while i <= len(s):\n        i => j\n        while j < len(s) and s[j] != \",\":\n            j + 1 => j\n        words(s[i:j]) => w\n        if len(w) == 0 or len(w) > 2 or column(w[0]) == -1:\n            return []\n        False => down\n        if len(w) == 2:\n            if w[1] == \"desc\":\n                True => down\n            elif w[1] != \"asc\":\n                return []\n        for k in out:\n            if k[0] == column(w[0]):\n                return []\n        out + [[column(w[0]), down]] => out\n        j + 1 => i\n    return out\n\ndef show(rows):\n    \"  name    team    year  score\" ^0\n    for r in rows:\n        (\"  \" + pad(r[0], 8) + pad(r[1], 8) + str(r[2]) + lpad(str(r[3]), 7)) ^0\n\ndef key_text(k):\n    [\"name\", \"team\", \"year\", \"score\"] => names\n    if k[1]:\n        return names[k[0]] + \" (down)\"\n    return names[k[0]]\n\ndef sort_table(rows):\n    while True:\n        trim(input(\"sort by (like: team, score desc)> \")) => s\n        if s == \"\":\n            \"Cancelled.\" ^0\n            return rows\n        keys_of(s) => keys\n        if len(keys) > 0:\n            sorting.sort_rows(rows, keys) => out\n            show(out)\n            if len(keys) > 1:\n                \"\" => order\n                len(keys) - 1 => k\n                while k >= 0:\n                    if order != \"\":\n                        order + \", then by \" => order\n                    order + key_text(keys[k]) => order\n                    k - 1 => k\n                if sorting.sort_passes(rows, keys) == out:\n                    (\"Sorting by \" + order + \" - one stable sort each, last column first - gives the same order.\") ^0\n                else:\n                    (\"Sorting by \" + order + \" gives a different order.\") ^0\n            return out\n        \"Columns are name, team, year and score, each once, then asc or desc, separated by commas.\" ^0\n\ndef stability(rows):\n    while True:\n        trim(input(\"column> \")) => s\n        if s == \"\":\n            \"Cancelled.\" ^0\n            return\n        column(s) => col\n        if col >= 0:\n            sorting.sort_rows(rows, [[col, False]]) => good\n            sorting.by_column_unstable(rows, col) => bad\n            \"  stable (> in the guard)       unstable (>= in the guard)\" ^0\n            for i in [0:len(rows) - 1]:\n                (\"  \" + pad(good[i][0], 8) + pad(str(good[i][col]), 22) + pad(bad[i][0], 8) + str(bad[i][col])) ^0\n            0 => moved\n            for i in [0:len(rows) - 1]:\n                if good[i][0] != bad[i][0]:\n                    moved + 1 => moved\n            if moved == 0:\n                (\"No two rows share a \" + s + \", so the two orders are the same.\") ^0\n            else:\n                (\"Both are sorted by \" + s + \", but \" + str(moved) + \" rows are in different places: the stable sort keeps equal rows in the order the table had them, the other reverses them.\") ^0\n            return\n        \"Columns are name, team, year and score.\" ^0\n\ndef counts():\n    while True:\n        trim(input(\"how many numbers (2 to 200)> \")) => s\n        if s == \"\":\n            \"Cancelled.\" ^0\n            return\n        number(s) => n\n        if n >= 2 and n <= 200:\n            while True:\n                trim(input(\"order (random, sorted, reversed, few)> \")) => order\n                if order == \"\":\n                    \"Cancelled.\" ^0\n                    return\n                if order == \"random\" or order == \"sorted\" or order == \"reversed\" or order == \"few\":\n                    sorting.numbers(n, order, n) => xs\n                    sorting.selection_count(xs) => a\n                    sorting.insertion_count(xs) => b\n                    sorting.merge_count(xs) => c\n                    sorting.quick_count(xs) => d\n                    (str(n) + \" numbers, \" + order + \":\") ^0\n                    (\"  selection \" + lpad(str(a[1]), 7) + \" comparisons\") ^0\n                    (\"  insertion \" + lpad(str(b[1]), 7)) ^0\n                    (\"  merge     \" + lpad(str(c[1]), 7)) ^0\n                    (\"  quicksort \" + lpad(str(d[1]), 7)) ^0\n                    if a[0] == b[0] and b[0] == c[0] and c[0] == d[0]:\n                        \"All four give the same sorted list.\" ^0\n                    # n (n - 1) is below 40,000 here, so the division is exact\n                    (\"n(n-1)/2 = \" + str(int(n * (n - 1) / 2)) + \": selection sort always makes exactly that many.\") ^0\n                    return\n                \"Type random, sorted, reversed or few.\" ^0\n        \"Type a whole number from 2 to 200.\" ^0\n\n[\n    [\"Ana\", \"red\", 2019, 88],\n    [\"Ben\", \"blue\", 2021, 92],\n    [\"Chloe\", \"red\", 2020, 92],\n    [\"Dev\", \"green\", 2019, 75],\n    [\"Eli\", \"blue\", 2019, 88],\n    [\"Fay\", \"green\", 2021, 92],\n    [\"Gus\", \"red\", 2021, 75],\n    [\"Hana\", \"blue\", 2020, 81],\n] => start\nstart => rows\n\n\"== Table sorter ==\" ^0\n\"Sort a table by several columns, see what a stable sort keeps, and count comparisons.\" ^0\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) table  2) sort  3) stable or not  4) count comparisons  5) back to the first order  6) quit\" ^0\n    trim(input(\"choice> \")) => choice\n    if choice == \"1\":\n        show(rows)\n    elif choice == \"2\":\n        sort_table(rows) => rows\n    elif choice == \"3\":\n        stability(rows)\n    elif choice == \"4\":\n        counts()\n    elif choice == \"5\":\n        start => rows\n        \"Back to the order the table started in.\" ^0\n    elif choice == \"6\":\n        False => running\n    else:\n        \"Pick a number from 1 to 6.\" ^0\n\"Bye.\" ^0\n",
      "python": "import sorting\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 number(s):\n    if len(s) == 0 or len(s) > 4:\n        return -1\n    n = 0\n    for c in s:\n        if not (c >= \"0\" and c <= \"9\"):\n            return -1\n        n = n * 10 + int(c)\n    return n\n\ndef pad(s, n):\n    while len(s) < n:\n        s = s + \" \"\n    return s\n\ndef lpad(s, n):\n    while len(s) < n:\n        s = \" \" + s\n    return s\n\ndef column(name):\n    if name == \"name\":\n        return 0\n    if name == \"team\":\n        return 1\n    if name == \"year\":\n        return 2\n    if name == \"score\":\n        return 3\n    return -1\n\ndef words(s):\n    out = []\n    w = \"\"\n    for c in s + \" \":\n        if c == \" \":\n            if w != \"\":\n                out = out + [w]\n            w = \"\"\n        else:\n            w = w + c\n    return out\n\ndef keys_of(s):\n    out = []\n    i = 0\n    while i <= len(s):\n        j = i\n        while j < len(s) and s[j] != \",\":\n            j = j + 1\n        w = words(s[i:j])\n        if len(w) == 0 or len(w) > 2 or column(w[0]) == -1:\n            return []\n        down = False\n        if len(w) == 2:\n            if w[1] == \"desc\":\n                down = True\n            elif w[1] != \"asc\":\n                return []\n        for k in out:\n            if k[0] == column(w[0]):\n                return []\n        out = out + [[column(w[0]), down]]\n        i = j + 1\n    return out\n\ndef show(rows):\n    print(\"  name    team    year  score\")\n    for r in rows:\n        print(\"  \" + pad(r[0], 8) + pad(r[1], 8) + str(r[2]) + lpad(str(r[3]), 7))\n\ndef key_text(k):\n    names = [\"name\", \"team\", \"year\", \"score\"]\n    if k[1]:\n        return names[k[0]] + \" (down)\"\n    return names[k[0]]\n\ndef sort_table(rows):\n    while True:\n        s = trim(input(\"sort by (like: team, score desc)> \"))\n        if s == \"\":\n            print(\"Cancelled.\")\n            return rows\n        keys = keys_of(s)\n        if len(keys) > 0:\n            out = sorting.sort_rows(rows, keys)\n            show(out)\n            if len(keys) > 1:\n                order = \"\"\n                k = len(keys) - 1\n                while k >= 0:\n                    if order != \"\":\n                        order = order + \", then by \"\n                    order = order + key_text(keys[k])\n                    k = k - 1\n                if sorting.sort_passes(rows, keys) == out:\n                    print(\"Sorting by \" + order + \" - one stable sort each, last column first - gives the same order.\")\n                else:\n                    print(\"Sorting by \" + order + \" gives a different order.\")\n            return out\n        print(\"Columns are name, team, year and score, each once, then asc or desc, separated by commas.\")\n\ndef stability(rows):\n    while True:\n        s = trim(input(\"column> \"))\n        if s == \"\":\n            print(\"Cancelled.\")\n            return\n        col = column(s)\n        if col >= 0:\n            good = sorting.sort_rows(rows, [[col, False]])\n            bad = sorting.by_column_unstable(rows, col)\n            print(\"  stable (> in the guard)       unstable (>= in the guard)\")\n            for i in range(0, len(rows)):\n                print(\"  \" + pad(good[i][0], 8) + pad(str(good[i][col]), 22) + pad(bad[i][0], 8) + str(bad[i][col]))\n            moved = 0\n            for i in range(0, len(rows)):\n                if good[i][0] != bad[i][0]:\n                    moved = moved + 1\n            if moved == 0:\n                print(\"No two rows share a \" + s + \", so the two orders are the same.\")\n            else:\n                print(\"Both are sorted by \" + s + \", but \" + str(moved) + \" rows are in different places: the stable sort keeps equal rows in the order the table had them, the other reverses them.\")\n            return\n        print(\"Columns are name, team, year and score.\")\n\ndef counts():\n    while True:\n        s = trim(input(\"how many numbers (2 to 200)> \"))\n        if s == \"\":\n            print(\"Cancelled.\")\n            return\n        n = number(s)\n        if n >= 2 and n <= 200:\n            while True:\n                order = trim(input(\"order (random, sorted, reversed, few)> \"))\n                if order == \"\":\n                    print(\"Cancelled.\")\n                    return\n                if order == \"random\" or order == \"sorted\" or order == \"reversed\" or order == \"few\":\n                    xs = sorting.numbers(n, order, n)\n                    a = sorting.selection_count(xs)\n                    b = sorting.insertion_count(xs)\n                    c = sorting.merge_count(xs)\n                    d = sorting.quick_count(xs)\n                    print(str(n) + \" numbers, \" + order + \":\")\n                    print(\"  selection \" + lpad(str(a[1]), 7) + \" comparisons\")\n                    print(\"  insertion \" + lpad(str(b[1]), 7))\n                    print(\"  merge     \" + lpad(str(c[1]), 7))\n                    print(\"  quicksort \" + lpad(str(d[1]), 7))\n                    if a[0] == b[0] and b[0] == c[0] and c[0] == d[0]:\n                        print(\"All four give the same sorted list.\")\n                    print(\"n(n-1)/2 = \" + str(int(n * (n - 1) / 2)) + \": selection sort always makes exactly that many.\")\n                    return\n                print(\"Type random, sorted, reversed or few.\")\n        print(\"Type a whole number from 2 to 200.\")\n\nstart = [[\"Ana\", \"red\", 2019, 88], [\"Ben\", \"blue\", 2021, 92], [\"Chloe\", \"red\", 2020, 92], [\"Dev\", \"green\", 2019, 75], [\"Eli\", \"blue\", 2019, 88], [\"Fay\", \"green\", 2021, 92], [\"Gus\", \"red\", 2021, 75], [\"Hana\", \"blue\", 2020, 81]]\nrows = start\nprint(\"== Table sorter ==\")\nprint(\"Sort a table by several columns, see what a stable sort keeps, and count comparisons.\")\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) table  2) sort  3) stable or not  4) count comparisons  5) back to the first order  6) quit\")\n    choice = trim(input(\"choice> \"))\n    if choice == \"1\":\n        show(rows)\n    elif choice == \"2\":\n        rows = sort_table(rows)\n    elif choice == \"3\":\n        stability(rows)\n    elif choice == \"4\":\n        counts()\n    elif choice == \"5\":\n        rows = start\n        print(\"Back to the order the table started in.\")\n    elif choice == \"6\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 6.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "sorting.eml",
      "eml": "# P072 table sorter - sorting rows by several columns, what stability\n# means, and how many comparisons four sorts make.\n\ndef copy_of(xs):\n    # The corpus case sort-stability-witness: `xs => out` would bind the\n    # same list, and a sort writing through its indices would change its\n    # own input. So a sort works on a copy.\n    [0] * len(xs) => out\n    for i in [0:len(xs) - 1]:\n        xs[i] => out[i]\n    return out\n\ndef before(a, b, keys):\n    # True when row a goes strictly before row b. keys is a list of\n    # [column, descending]; the first column where they differ decides.\n    for k in keys:\n        a[k[0]] => x\n        b[k[0]] => y\n        if x != y:\n            if k[1]:\n                return x > y\n            return x < y\n    return False\n\ndef sort_rows(rows, keys):\n    # Insertion sort on all the keys at once. A row moves left only past\n    # rows it goes strictly before, so rows equal on every key keep their\n    # order: the sort is stable.\n    copy_of(rows) => out\n    for i in [1:len(out) - 1]:\n        out[i] => x\n        i - 1 => j\n        while j >= 0 and before(x, out[j], keys):\n            out[j] => out[j + 1]\n            j - 1 => j\n        x => out[j + 1]\n    return out\n\ndef sort_passes(rows, keys):\n    # The same order by one stable sort per key, the LAST key first: each\n    # pass keeps the order the earlier passes made among its ties.\n    rows => out\n    len(keys) - 1 => k\n    while k >= 0:\n        sort_rows(out, [keys[k]]) => out\n        k - 1 => k\n    return out\n\ndef by_column_unstable(rows, col):\n    # Insertion sort with >= instead of > in the guard: it still sorts, but\n    # it moves a row past EQUAL rows too, reversing ties.\n    copy_of(rows) => out\n    for i in [1:len(out) - 1]:\n        out[i] => x\n        i - 1 => j\n        while j >= 0 and out[j][col] >= x[col]:\n            out[j] => out[j + 1]\n            j - 1 => j\n        x => out[j + 1]\n    return out\n\n# The counting sorts: each returns [sorted copy, comparisons made].\n\ndef selection_count(xs):\n    copy_of(xs) => a\n    0 => c\n    for i in [0:len(a) - 2]:\n        i => m\n        for j in [i + 1:len(a) - 1]:\n            c + 1 => c\n            if a[j] < a[m]:\n                j => m\n        a[i] => t\n        a[m] => a[i]\n        t => a[m]\n    return [a, c]\n\ndef insertion_count(xs):\n    copy_of(xs) => a\n    0 => c\n    for i in [1:len(a) - 1]:\n        a[i] => x\n        i - 1 => j\n        True => going\n        while going and j >= 0:\n            c + 1 => c\n            if a[j] > x:\n                a[j] => a[j + 1]\n                j - 1 => j\n            else:\n                False => going\n        x => a[j + 1]\n    return [a, c]\n\ndef merge_count(xs):\n    # Top-down merge sort; the counter is a one-element list so the halves\n    # can add to it.\n    [0] => c\n    return [merge_sorted(xs, c), c[0]]\n\ndef merge_sorted(xs, c):\n    if len(xs) <= 1:\n        return copy_of(xs)\n    int(len(xs) / 2) => h\n    merge_sorted(xs[0:h], c) => left\n    merge_sorted(xs[h:len(xs)], c) => right\n    [0] * len(xs) => out\n    0 => i\n    0 => j\n    0 => k\n    while i < len(left) and j < len(right):\n        c[0] + 1 => c[0]\n        if right[j] < left[i]:\n            right[j] => out[k]\n            j + 1 => j\n        else:\n            left[i] => out[k]\n            i + 1 => i\n        k + 1 => k\n    while i < len(left):\n        left[i] => out[k]\n        i + 1 => i\n        k + 1 => k\n    while j < len(right):\n        right[j] => out[k]\n        j + 1 => j\n        k + 1 => k\n    return out\n\ndef quick_count(xs):\n    # The corpus case quicksort-recursive: Lomuto partition around the last\n    # element. On a list already in order every pivot is the largest, so it\n    # makes n(n-1)/2 comparisons, as many as selection sort.\n    copy_of(xs) => a\n    [0] => c\n    quick(a, 0, len(a) - 1, c)\n    return [a, c[0]]\n\ndef quick(a, lo, hi, c):\n    if lo < hi:\n        a[hi] => pivot\n        lo - 1 => i\n        for j in [lo:hi - 1]:\n            c[0] + 1 => c[0]\n            if a[j] <= pivot:\n                i + 1 => i\n                a[i] => t\n                a[j] => a[i]\n                t => a[j]\n        a[i + 1] => t\n        a[hi] => a[i + 1]\n        t => a[hi]\n        quick(a, lo, i, c)\n        quick(a, i + 2, hi, c)\n\ndef numbers(n, order, seed):\n    # n test numbers: \"random\" from a linear congruential generator (the\n    # constants of Numerical Recipes, mod 2^32), \"sorted\", \"reversed\", or\n    # \"few\" (random, but only 5 different values). The low bits of such a\n    # generator repeat quickly, so the value is taken from the top 16 bits;\n    # x / 65536 divides by a power of two below 2^53, which is exact.\n    [0] * n => out\n    seed => x\n    for i in [0:n - 1]:\n        (1664525 * x + 1013904223) % 4294967296 => x\n        if order == \"sorted\":\n            i + 1 => out[i]\n        elif order == \"reversed\":\n            n - i => out[i]\n        elif order == \"few\":\n            int(x / 65536) % 5 => out[i]\n        else:\n            int(x / 65536) % 1000 => out[i]\n    return out\n",
      "python": "def copy_of(xs):\n    out = [0] * len(xs)\n    for i in range(0, len(xs)):\n        out[i] = xs[i]\n    return out\n\ndef before(a, b, keys):\n    for k in keys:\n        x = a[k[0]]\n        y = b[k[0]]\n        if x != y:\n            if k[1]:\n                return x > y\n            return x < y\n    return False\n\ndef sort_rows(rows, keys):\n    out = copy_of(rows)\n    for i in range(1, len(out)):\n        x = out[i]\n        j = i - 1\n        while j >= 0 and before(x, out[j], keys):\n            out[j + 1] = out[j]\n            j = j - 1\n        out[j + 1] = x\n    return out\n\ndef sort_passes(rows, keys):\n    out = rows\n    k = len(keys) - 1\n    while k >= 0:\n        out = sort_rows(out, [keys[k]])\n        k = k - 1\n    return out\n\ndef by_column_unstable(rows, col):\n    out = copy_of(rows)\n    for i in range(1, len(out)):\n        x = out[i]\n        j = i - 1\n        while j >= 0 and out[j][col] >= x[col]:\n            out[j + 1] = out[j]\n            j = j - 1\n        out[j + 1] = x\n    return out\n\ndef selection_count(xs):\n    a = copy_of(xs)\n    c = 0\n    for i in range(0, len(a) - 2+1):\n        m = i\n        for j in range(i + 1, len(a)):\n            c = c + 1\n            if a[j] < a[m]:\n                m = j\n        t = a[i]\n        a[i] = a[m]\n        a[m] = t\n    return [a, c]\n\ndef insertion_count(xs):\n    a = copy_of(xs)\n    c = 0\n    for i in range(1, len(a)):\n        x = a[i]\n        j = i - 1\n        going = True\n        while going and j >= 0:\n            c = c + 1\n            if a[j] > x:\n                a[j + 1] = a[j]\n                j = j - 1\n            else:\n                going = False\n        a[j + 1] = x\n    return [a, c]\n\ndef merge_count(xs):\n    c = [0]\n    return [merge_sorted(xs, c), c[0]]\n\ndef merge_sorted(xs, c):\n    if len(xs) <= 1:\n        return copy_of(xs)\n    h = int(len(xs) / 2)\n    left = merge_sorted(xs[0:h], c)\n    right = merge_sorted(xs[h:len(xs)], c)\n    out = [0] * len(xs)\n    i = 0\n    j = 0\n    k = 0\n    while i < len(left) and j < len(right):\n        c[0] = c[0] + 1\n        if right[j] < left[i]:\n            out[k] = right[j]\n            j = j + 1\n        else:\n            out[k] = left[i]\n            i = i + 1\n        k = k + 1\n    while i < len(left):\n        out[k] = left[i]\n        i = i + 1\n        k = k + 1\n    while j < len(right):\n        out[k] = right[j]\n        j = j + 1\n        k = k + 1\n    return out\n\ndef quick_count(xs):\n    a = copy_of(xs)\n    c = [0]\n    quick(a, 0, len(a) - 1, c)\n    return [a, c[0]]\n\ndef quick(a, lo, hi, c):\n    if lo < hi:\n        pivot = a[hi]\n        i = lo - 1\n        for j in range(lo, hi):\n            c[0] = c[0] + 1\n            if a[j] <= pivot:\n                i = i + 1\n                t = a[i]\n                a[i] = a[j]\n                a[j] = t\n        t = a[i + 1]\n        a[i + 1] = a[hi]\n        a[hi] = t\n        quick(a, lo, i, c)\n        quick(a, i + 2, hi, c)\n\ndef numbers(n, order, seed):\n    out = [0] * n\n    x = seed\n    for i in range(0, n):\n        x = (1664525 * x + 1013904223) % 4294967296\n        if order == \"sorted\":\n            out[i] = i + 1\n        elif order == \"reversed\":\n            out[i] = n - i\n        elif order == \"few\":\n            out[i] = int(x / 65536) % 5\n        else:\n            out[i] = int(x / 65536) % 1000\n    return out\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n2\ncolour\nteam up\nteam, team\nteam,\n\n3\ncolour\nname\n4\n1\n201\nabc\n50\nshuffled\n\n6\n",
      "screen": "== Table sorter ==\nSort a table by several columns, see what a stable sort keeps, and count comparisons.\n\n1) table  2) sort  3) stable or not  4) count comparisons  5) back to the first order  6) quit\nchoice> 0\nPick a number from 1 to 6.\n\n1) table  2) sort  3) stable or not  4) count comparisons  5) back to the first order  6) quit\nchoice> x\nPick a number from 1 to 6.\n\n1) table  2) sort  3) stable or not  4) count comparisons  5) back to the first order  6) quit\nchoice> 2\nsort by (like: team, score desc)> colour\nColumns are name, team, year and score, each once, then asc or desc, separated by commas.\nsort by (like: team, score desc)> team up\nColumns are name, team, year and score, each once, then asc or desc, separated by commas.\nsort by (like: team, score desc)> team, team\nColumns are name, team, year and score, each once, then asc or desc, separated by commas.\nsort by (like: team, score desc)> team,\nColumns are name, team, year and score, each once, then asc or desc, separated by commas.\nsort by (like: team, score desc)> \nCancelled.\n\n1) table  2) sort  3) stable or not  4) count comparisons  5) back to the first order  6) quit\nchoice> 3\ncolumn> colour\nColumns are name, team, year and score.\ncolumn> name\n  stable (> in the guard)       unstable (>= in the guard)\n  Ana     Ana                   Ana     Ana\n  Ben     Ben                   Ben     Ben\n  Chloe   Chloe                 Chloe   Chloe\n  Dev     Dev                   Dev     Dev\n  Eli     Eli                   Eli     Eli\n  Fay     Fay                   Fay     Fay\n  Gus     Gus                   Gus     Gus\n  Hana    Hana                  Hana    Hana\nNo two rows share a name, so the two orders are the same.\n\n1) table  2) sort  3) stable or not  4) count comparisons  5) back to the first order  6) quit\nchoice> 4\nhow many numbers (2 to 200)> 1\nType a whole number from 2 to 200.\nhow many numbers (2 to 200)> 201\nType a whole number from 2 to 200.\nhow many numbers (2 to 200)> abc\nType a whole number from 2 to 200.\nhow many numbers (2 to 200)> 50\norder (random, sorted, reversed, few)> shuffled\nType random, sorted, reversed or few.\norder (random, sorted, reversed, few)> \nCancelled.\n\n1) table  2) sort  3) stable or not  4) count comparisons  5) back to the first order  6) quit\nchoice> 6\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\n2\nteam, score desc\n3\nscore\n5\n2\nyear desc, name\n4\n200\nsorted\n4\n200\nrandom\n4\n50\nreversed\n4\n100\nfew\n6\n",
      "screen": "== Table sorter ==\nSort a table by several columns, see what a stable sort keeps, and count comparisons.\n\n1) table  2) sort  3) stable or not  4) count comparisons  5) back to the first order  6) quit\nchoice> 1\n  name    team    year  score\n  Ana     red     2019     88\n  Ben     blue    2021     92\n  Chloe   red     2020     92\n  Dev     green   2019     75\n  Eli     blue    2019     88\n  Fay     green   2021     92\n  Gus     red     2021     75\n  Hana    blue    2020     81\n\n1) table  2) sort  3) stable or not  4) count comparisons  5) back to the first order  6) quit\nchoice> 2\nsort by (like: team, score desc)> team, score desc\n  name    team    year  score\n  Ben     blue    2021     92\n  Eli     blue    2019     88\n  Hana    blue    2020     81\n  Fay     green   2021     92\n  Dev     green   2019     75\n  Chloe   red     2020     92\n  Ana     red     2019     88\n  Gus     red     2021     75\nSorting by score (down), then by team - one stable sort each, last column first - gives the same order.\n\n1) table  2) sort  3) stable or not  4) count comparisons  5) back to the first order  6) quit\nchoice> 3\ncolumn> score\n  stable (> in the guard)       unstable (>= in the guard)\n  Dev     75                    Gus     75\n  Gus     75                    Dev     75\n  Hana    81                    Hana    81\n  Eli     88                    Ana     88\n  Ana     88                    Eli     88\n  Ben     92                    Chloe   92\n  Fay     92                    Fay     92\n  Chloe   92                    Ben     92\nBoth are sorted by score, but 6 rows are in different places: the stable sort keeps equal rows in the order the table had them, the other reverses them.\n\n1) table  2) sort  3) stable or not  4) count comparisons  5) back to the first order  6) quit\nchoice> 5\nBack to the order the table started in.\n\n1) table  2) sort  3) stable or not  4) count comparisons  5) back to the first order  6) quit\nchoice> 2\nsort by (like: team, score desc)> year desc, name\n  name    team    year  score\n  Ben     blue    2021     92\n  Fay     green   2021     92\n  Gus     red     2021     75\n  Chloe   red     2020     92\n  Hana    blue    2020     81\n  Ana     red     2019     88\n  Dev     green   2019     75\n  Eli     blue    2019     88\nSorting by name, then by year (down) - one stable sort each, last column first - gives the same order.\n\n1) table  2) sort  3) stable or not  4) count comparisons  5) back to the first order  6) quit\nchoice> 4\nhow many numbers (2 to 200)> 200\norder (random, sorted, reversed, few)> sorted\n200 numbers, sorted:\n  selection   19900 comparisons\n  insertion     199\n  merge         732\n  quicksort   19900\nAll four give the same sorted list.\nn(n-1)/2 = 19900: selection sort always makes exactly that many.\n\n1) table  2) sort  3) stable or not  4) count comparisons  5) back to the first order  6) quit\nchoice> 4\nhow many numbers (2 to 200)> 200\norder (random, sorted, reversed, few)> random\n200 numbers, random:\n  selection   19900 comparisons\n  insertion    9990\n  merge        1288\n  quicksort    1857\nAll four give the same sorted list.\nn(n-1)/2 = 19900: selection sort always makes exactly that many.\n\n1) table  2) sort  3) stable or not  4) count comparisons  5) back to the first order  6) quit\nchoice> 4\nhow many numbers (2 to 200)> 50\norder (random, sorted, reversed, few)> reversed\n50 numbers, reversed:\n  selection    1225 comparisons\n  insertion    1225\n  merge         153\n  quicksort    1225\nAll four give the same sorted list.\nn(n-1)/2 = 1225: selection sort always makes exactly that many.\n\n1) table  2) sort  3) stable or not  4) count comparisons  5) back to the first order  6) quit\nchoice> 4\nhow many numbers (2 to 200)> 100\norder (random, sorted, reversed, few)> few\n100 numbers, few:\n  selection    4950 comparisons\n  insertion    1961\n  merge         515\n  quicksort    1149\nAll four give the same sorted list.\nn(n-1)/2 = 4950: selection sort always makes exactly that many.\n\n1) table  2) sort  3) stable or not  4) count comparisons  5) back to the first order  6) quit\nchoice> 6\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "sort-stability-witness",
      "caseId": "211-sort-stability-witness",
      "title": "Sorted is not the same as stable"
    },
    {
      "slug": "quicksort-recursive",
      "caseId": "075-quicksort-recursive",
      "title": "Quicksort (recursive)"
    }
  ],
  "updated": "2026-10-14"
}
