Correctness

Assertions, invariants, and a test that locks the rule.

An algorithm is correct if it meets its spec for every allowed input. Assertions catch violations while you learn. An invariant is a fact that stays true as the loop runs.

Goal

Assert binary search on a sorted list, and lock a spec with unittest.

Assert the spec

def binary_search(items, target):
    assert items == sorted(items), "binary search needs sorted items"
    lo, hi = 0, len(items) - 1
    while lo <= hi:
        mid = (lo + hi) // 2
        if items[mid] == target:
            return mid
        if items[mid] < target:
            lo = mid + 1
        else:
            hi = mid - 1
    return -1

print(binary_search(["Kisumu", "Mombasa", "Nairobi"], "Mombasa"))

Invariant (loop)

def maximum(xs):
    assert xs, "maximum of empty list"
    best = xs[0]
    for i, x in enumerate(xs):
        if x > best:
            best = x
        assert best == max(xs[: i + 1])
    return best

print(maximum([10.5, 31.0, 22.0]))

After each index, best is the max of the prefix seen so far.

A test

import unittest

def linear_search(items, target):
    for i, item in enumerate(items):
        if item == target:
            return i
    return -1

class SearchTests(unittest.TestCase):
    def test_hit(self):
        self.assertEqual(linear_search(["A", "B", "C"], "B"), 1)

    def test_miss(self):
        self.assertEqual(linear_search(["A", "B"], "Z"), -1)

    def test_first_duplicate(self):
        self.assertEqual(linear_search([10.5, 22.0, 10.5], 10.5), 0)

suite = unittest.defaultTestLoader.loadTestsFromTestCase(SearchTests)
result = unittest.TextTestRunner(verbosity=2).run(suite)
print("ok" if result.wasSuccessful() else "failed")
Pitfall

An assert is not a substitute for thinking about empty lists and duplicates. Write those cases down, then test them.