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.