Skip to content

Commit 3478f87

Browse files
authored
Enhance type safety and clarity in benchmark_sorts.py
Updated the benchmark_sorts.py script to improve type safety and added a Comparable protocol for generic sorting. Adjusted comments for clarity and fixed minor formatting issues.
1 parent 6a662aa commit 3478f87

1 file changed

Lines changed: 29 additions & 7 deletions

File tree

sorts/benchmark_sorts.py

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1+
#!/usr/bin/env -S uv run --script
2+
13
"""
24
Benchmark several sorting algorithms on the same random datasets.
35
46
This is a *reference* benchmark, not a rigorous one: it times each algorithm on a
57
few shared, randomly generated integer datasets and prints a small comparison
68
table. It exists so that visitors can see the practical cost of the different
79
strategies 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
1113
Run it from the repository root:
1214
@@ -16,13 +18,12 @@
1618
re-implements a sort.
1719
"""
1820

19-
from __future__ import annotations
20-
2121
import random
2222
import sys
2323
from collections.abc import Callable, Sequence
2424
from itertools import pairwise
2525
from timeit import timeit
26+
from typing import Protocol
2627

2728
from sorts.bubble_sort import bubble_sort_iterative
2829
from 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

101123
def 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

Comments
 (0)