CSCI 0190 · Fall 2026

Sorting Robustness🔗

    1 What is This Assignment’s Purpose?

    2 Theme Song

    3 Problem Setup

      3.1 The Deck

      3.2 The Faulty Comparator

      3.3 Measuring Wrongness

      3.4 Termination

    4 Assignment

    5 Testing

    6 Analysis

    7 Built-Ins

    8 Starter

1 What is This Assignment’s Purpose?🔗

Sometimes, we have to operate computers in faulty environments, usually caused by physical devices (everything from sensors breaking to devices being miscalibrated to a stream of cosmic rays). This can cause computations to produce faulty answers. In this assignment, we’re going to study that question in a limited setting where we can easily simulate the problem and assess the outcomes.Many thanks to David Ackley! This assignment is entirely due to him. Specifically, we are going to measure robustness: how gracefully an algorithm degrades in the face of faults.

2 Theme Song🔗

BIRDS OF A FEATHER by Billie Eilish

3 Problem Setup🔗

3.1 The Deck🔗

For simplicity, we’ll assume we’re sorting a card deck, where the cards are numbered \(0\) through \(n-1\). Assume we are sorting in ascending order. Thus, the correctly-sorted deck is just \(0, 1, 2, \dots, n-1\), so the value \(v\) belongs at position \(v\). Fixing this lets us vary the other parameters while giving us an easy way to measure “wrongness”. We’ll call a deck’s type Deck:

type Deck = List<Number>

3.2 The Faulty Comparator🔗

A comparator is a function that answers whether its first argument should come before its second:

type Comparator = (Number, Number -> Boolean)

An honest comparator on numbers is just lam(a, b): a < b end.

We are interested in faulty comparators. Given a fault rate \(p\) (a number between \(0\) and \(1\)), on each call, with probability \(p\) it ignores its arguments entirely and returns a random Boolean; otherwise it answers honestly.For the mathy folks: we are assuming that the faults are independent and identically distributed — but many real-world faults are not! Observe that asking the same question twice can get you two different answers.

3.3 Measuring Wrongness🔗

We score an output deck by its total positional displacement: for each position, how far is the value sitting there from where it belongs? Since the value \(v\) belongs at position \(v\), if \(out\) is the output list then

\[E \;=\; \sum_{i} \left| out_i - i \right|.\]

A perfectly sorted deck scores \(0\). A completely reversed deck scores \(\lfloor n^2/2 \rfloor\) (for \(n = 52\), that is \(1352\)), which is the worst you can do. Every other output lands somewhere in between. Think of it as how far every card has to travel to “get home”.

3.4 Termination🔗

Make sure your implementations halt! With honest comparators, they all do (otherwise they wouldn’t be part of our canon). But with a faulty comparator, it’s no longer guaranteed. Suppose your algorithm requires you to traverse the whole list to check whether it’s sorted. But how are you checking for sortedness? Using the faulty comparator. That means there’s a chance that a list that is not sorted will be declared sorted — problematic for correctness, but not for termination. But a list that is sorted may be declared to not be, forcing more work when it should have halted, and this can go on indefinitely.

Therefore, ensure that every algorithm you write terminates on every input for every fault rate, including a comparator that lies on every single call. Obviously, it may not produce the right answer!

4 Assignment🔗

  1. The sorters. A sorter takes a deck and a comparator (honest or faulty) and returns a deck. We’ll name that type as well:

    type Sorter = (Deck, Comparator -> Deck)

    Implement each of the following sorting algorithms as a Sorter. You can look up the algorithms on Wikipedia, but be sure to implement them following the style rules of this course:
    • bubble sort, doing a fixed number of passes (see the termination rule above)—not “until sorted”;

    • insertion sort;

    • selection sort (remove may come in handy);

    • merge sort;

    • quick sort. Partition so that each element is compared to the pivot exactly once. Otherwise, with a faulty comparator, an element can end up in both halves or neither. You may use partition for this—it calls its predicate exactly once per element, which is exactly the property you want.

    By passing the comparator as an argument, we can swap out different comparators without changing the underlying sorting code.

  2. Preregistration. Preregister your beliefs. How do you expect the above algorithms to fare? Why? Write down what you think/expect before you continue! You will turn this in later; save it and don’t modify it.

  3. The faulty comparator. Write a function that, given a fault rate, produces a comparator with that rate:

    make-noisy-lt :: (Number -> Comparator)

    Use num-random for the coin flips. Because num-random draws from a global source, calling num-random-seed once at the top of a run makes that run reproduciblevery handy when debugging a “why did I get a weird graph” moment.

  4. The metric. Write

    positional-error :: (Deck -> Number)

    computing \(E\) above.

  5. The harness. Put it together:

    average-error :: (Sorter, List<Deck>, Number -> Number)

    The first parameter is a sorter. The second is a list of decks. The third is the fault rate \(p\). The result is the average positional error when that sorter (using a fault-rate-\(p\) comparator) is run on each of those decks.

    Build your list of decks once, and use that same list for every algorithm and every fault rate. That way the comparison is paired: the algorithms all run on exactly the same decks, so a difference you see is due to the algorithm, not the luck of the shuffle. (The number of decks is your number of trials; use enough that the averages are stable.)

    Run this for each fault rate \(p \in \{0.1, 0.2, \dots, 0.9\}\), for each of the sorting algorithms.

  6. The more robust variant. A common way to handle unreliable components is by voting: ask the same question several times and take the majority answer. We can apply the same principle here. Write a wrapper that turns any comparator into a more reliable one by calling it \(k\) times and taking the majority vote:

    majority :: (Comparator, Number -> Comparator)

    (Use an odd \(k\) so there are no ties.) Now run your quick sort and merge sort again, but wrapping the noisy comparator in majority with \(k = 5\). Call these quick-maj5 and merge-maj5 and include them in your experiments.

  7. The graphs. Use the chart library (import chart as C). To save you the trouble of digging through documentation, here are the commands with placeholders.

    A single series is an x list and a y list:

    # one algorithm's curve: fault rates on x, its errors on y

    C.render-chart(

      C.from-list.line-plot(FAULT-RATES, ERRORS-FOR-ONE-ALGORITHM))

      .title("bubble sort")

      .x-axis("fault rate p")

      .y-axis("mean positional error")

      .display()

    To put every algorithm on one axis, make one line-plot per algorithm, give each a legend, and combine them with render-charts:

    C.render-charts(

      [list:

        C.from-list.line-plot(FAULT-RATES, ERRORS-BUBBLE).legend("bubble"),

        C.from-list.line-plot(FAULT-RATES, ERRORS-QUICK).legend("quick")

        # ... one per algorithm ...

        ])

      .title("sorting robustness")

      .x-axis("fault rate p")

      .y-axis("mean positional error")

      .display()

    A bar chart comparing the algorithms at a single fault rate takes a list of names and a list of values:

    C.render-chart(

      C.from-list.bar-chart(ALGORITHM-NAMES, ERRORS-AT-ONE-RATE))

      .title("error by algorithm at p = 0.1")

      .x-axis("algorithm")

      .y-axis("mean positional error")

      .display()

    Produce at least these three: a per-algorithm curve, the combined summary, and a bar chart at one fault rate (say \(p = 0.1\)). The full chart docs are here if you want to go further—colors, other chart types, and so on. Tip: to drop a chart into your report, right-click the rendered chart image and copy it.

5 Testing🔗

Because the experiments involve a good deal of randomness, you can’t really do much end-to-end testing. But the pieces are very testable, and you should test them:

  • Honest sorting. At fault rate \(0\), every sorter must sort perfectly.

  • The metric. A sorted deck scores \(0\); a reversed deck scores \(\lfloor n^2/2 \rfloor\); and so on.

  • Termination and permutation. Even with a comparator that lies on every call (fault rate \(1\)), each sorter must finish and must return a permutation of its input. Which subset of the properties from Sortacle still apply? Obviously not all, but also not none!

6 Analysis🔗

Write a brief report explaining what you found. A few paragraphs is fine, and definitely do not go over a page of prose (excluding the space taken up by charts). You are free to generate more charts than described above to provide a good explanation. Things to think about covering:
  • How did the different algorithms fare? How did the robustness change as the fault rate grew? Did it ever “flip” (e.g., most robust at lower fault rates but least robust at high rates)?

  • How well or poorly did the outcome match your preregistered beliefs?

  • Why do you think the algorithms fared as they did? What explains robustness/fragility?

  • Did computing the majority help?

Please clearly mark and append your preregistration document to your report. There are no “wrong answers” here! We are just curious to see how you thought about this initially.

7 Built-Ins🔗

You will want these three libraries:

import lists as L

import chart as C

import math as M

You may use:
  • the number and string libraries—including num-abs, num-floor, num-to-string, and (for the faulty comparator and for shuffling) num-random and num-random-seed;

  • from lists: map, filter, the folds, map2, range, length, reverse, shuffle, take, drop, get, and partition/remove if you find them handy;

  • from math: sum (that is, M.sum);

  • the chart library, for your graphs.

The sorting algorithms must be your own work: you may use the list helpers above as building blocks, but you should build the actual sorting algorithms yourself, not use ones from libraries. You may, of course, use sort and sort-by for testing.

8 Starter🔗

Starter (opens in a new tab)