1+ #!/usr/bin/env -S uv run --script
2+
13"""
24Benchmark several sorting algorithms on the same random datasets.
35
46This is a *reference* benchmark, not a rigorous one: it times each algorithm on a
57few shared, randomly generated integer datasets and prints a small comparison
68table. It exists so that visitors can see the practical cost of the different
79strategies in this directory side by side, without embedding timing code inside
8- the individual algorithm modules (which keeps those files clean, import-cheap and
9- focused on being readable reference implementations).
10+ the individual algorithm modules (which keeps those files clean, import-cheap,
11+ and focused on being readable reference implementations).
1012
1113Run it from the repository root:
1214
1618re-implements a sort.
1719"""
1820
19- from __future__ import annotations
20-
2121import random
2222import sys
2323from collections .abc import Callable , Sequence
2424from itertools import pairwise
2525from timeit import timeit
26+ from typing import Protocol
2627
2728from sorts .bubble_sort import bubble_sort_iterative
2829from sorts .cocktail_shaker_sort import cocktail_shaker_sort
@@ -73,25 +74,46 @@ def all_sorts_agree(data: list[int]) -> bool:
7374 Each algorithm is given a fresh copy of the data (some sort in place), and its
7475 result is checked against Python's built-in ``sorted`` as the ground truth.
7576
76- >>> all_sorts_agree([5, 1, 4, 2, 8, 0, 2])
77+ >>> all_sorts_agree([5, 1, 4.2 , 2, 8.5 , 0, 2])
7778 True
7879 >>> all_sorts_agree([])
7980 True
8081 >>> all_sorts_agree([42])
8182 True
83+ >>> all_sorts_agree(list(range(5, -6, -1)))
84+ True
85+ >>> all_sorts_agree(list("Python"))
86+ True
8287 """
8388 expected = sorted (data )
8489 return all (list (sort_fn (data .copy ())) == expected for sort_fn in SORTS .values ())
8590
8691
87- def benchmark (data : list [int ], number : int = 1 ) -> dict [str , float ]:
92+ class Comparable (Protocol ):
93+ def __lt__ (self , other : object , / ) -> bool : ...
94+
95+
96+ def benchmark [T : Comparable ](data : list [T ], number : int = 1 ) -> dict [str , float ]:
8897 """
8998 Time every algorithm in ``SORTS`` on a copy of ``data``.
9099
91100 Returns a mapping of algorithm name to the elapsed seconds for ``number``
92101 repetitions. Each timed call receives its own fresh copy so in-place sorts do
93102 not hand an already-sorted list to the next repetition.
103+
104+ >>> benchmark([])
105+ Traceback (most recent call last):
106+ ...
107+ ValueError: Please provide a non-empty dataset
108+ >>> benchmark([1], number=0)
109+ Traceback (most recent call last):
110+ ...
111+ ValueError: Number of repetitions must be positive
94112 """
113+ if not data :
114+ raise ValueError ("Please provide a non-empty dataset" )
115+ if number <= 0 :
116+ raise ValueError ("Number of repetitions must be positive" )
95117 timings : dict [str , float ] = {}
96118 for name , sort_fn in SORTS .items ():
97119 timings [name ] = timeit (lambda fn = sort_fn : fn (data .copy ()), number = number )
@@ -100,7 +122,7 @@ def benchmark(data: list[int], number: int = 1) -> dict[str, float]:
100122
101123def main () -> None :
102124 # A couple of the imported algorithms (e.g. tim_sort) merge recursively, so
103- # give them head-room to sort the largest dataset without hitting the limit.
125+ # give them headroom to sort the largest dataset without hitting the limit.
104126 sys .setrecursionlimit (10_000 )
105127 sizes = (100 , 1_000 , 3_000 )
106128 random .seed (0 )
0 commit comments