algo-compare 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- algo_compare-1.0.0/LICENSE +21 -0
- algo_compare-1.0.0/PKG-INFO +595 -0
- algo_compare-1.0.0/README.md +563 -0
- algo_compare-1.0.0/algo_compare.egg-info/PKG-INFO +595 -0
- algo_compare-1.0.0/algo_compare.egg-info/SOURCES.txt +26 -0
- algo_compare-1.0.0/algo_compare.egg-info/dependency_links.txt +1 -0
- algo_compare-1.0.0/algo_compare.egg-info/entry_points.txt +2 -0
- algo_compare-1.0.0/algo_compare.egg-info/requires.txt +8 -0
- algo_compare-1.0.0/algo_compare.egg-info/top_level.txt +1 -0
- algo_compare-1.0.0/algocomp/__init__.py +65 -0
- algo_compare-1.0.0/algocomp/algorithm.py +152 -0
- algo_compare-1.0.0/algocomp/benchmark.py +138 -0
- algo_compare-1.0.0/algocomp/catalog.py +1115 -0
- algo_compare-1.0.0/algocomp/cli.py +600 -0
- algo_compare-1.0.0/algocomp/comparator.py +737 -0
- algo_compare-1.0.0/algocomp/complexity.py +504 -0
- algo_compare-1.0.0/algocomp/curve_fit.py +173 -0
- algo_compare-1.0.0/algocomp/expr.py +279 -0
- algo_compare-1.0.0/algocomp/matrix.py +147 -0
- algo_compare-1.0.0/algocomp/py.typed +0 -0
- algo_compare-1.0.0/algocomp/registry.py +308 -0
- algo_compare-1.0.0/algocomp/reports.py +605 -0
- algo_compare-1.0.0/algocomp/static_analysis.py +520 -0
- algo_compare-1.0.0/algocomp/verify.py +640 -0
- algo_compare-1.0.0/pyproject.toml +47 -0
- algo_compare-1.0.0/setup.cfg +4 -0
- algo_compare-1.0.0/tests/test_algocomp.py +487 -0
- algo_compare-1.0.0/tests/test_extensions.py +210 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 algo-compare contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: algo-compare
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Theoretical complexity comparison framework: pit two algorithms that solve the same task against each other.
|
|
5
|
+
Author: 3punch
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/3punch/algocomp
|
|
8
|
+
Project-URL: Repository, https://github.com/3punch/algocomp
|
|
9
|
+
Project-URL: Issues, https://github.com/3punch/algocomp/issues
|
|
10
|
+
Keywords: algorithms,big-o,complexity,benchmark,education,static-analysis,curve-fitting
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Education
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Classifier: Topic :: Education
|
|
21
|
+
Classifier: Topic :: Software Development :: Testing
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Provides-Extra: pretty
|
|
26
|
+
Requires-Dist: rich>=13; extra == "pretty"
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: rich>=13; extra == "dev"
|
|
29
|
+
Requires-Dist: build; extra == "dev"
|
|
30
|
+
Requires-Dist: twine; extra == "dev"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# algocomp · `algo-compare`
|
|
34
|
+
|
|
35
|
+
**A theoretical complexity-comparison framework for algorithms that solve the same task.**
|
|
36
|
+
|
|
37
|
+

|
|
38
|
+

|
|
39
|
+

|
|
40
|
+

|
|
41
|
+
|
|
42
|
+
Give it two algorithms. It tells you who wins asymptotically, by how much, from which
|
|
43
|
+
input size the advantage actually matters, where the trade-offs are (memory, stability,
|
|
44
|
+
worst case), and what the caveats are — all from the *declared* Big-O profiles, with no
|
|
45
|
+
benchmark noise.
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
$ algo-compare compare merge_sort quick_sort
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
╭─────────────────────────────────────────────────────╮
|
|
53
|
+
│ Algorithm Comparison · Merge Sort vs Quick Sort │
|
|
54
|
+
╰─────────────────────────────────────────────────────╯
|
|
55
|
+
Complexity profiles
|
|
56
|
+
|
|
57
|
+
Merge Sort Quick Sort
|
|
58
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
59
|
+
category sorting sorting
|
|
60
|
+
task sort a list of comparable items sort a list of comparable items
|
|
61
|
+
time best O(n log n) O(n log n)
|
|
62
|
+
time average O(n log n) O(n log n)
|
|
63
|
+
time worst O(n log n) O(n^2)
|
|
64
|
+
space best O(n) O(log n)
|
|
65
|
+
space average O(n) O(log n)
|
|
66
|
+
space worst O(n) O(n)
|
|
67
|
+
stable yes no
|
|
68
|
+
in place no yes
|
|
69
|
+
input sensitive — yes — best/average/worst differ
|
|
70
|
+
|
|
71
|
+
Case-by-case verdict
|
|
72
|
+
╭─────────────┬───────────┬──────────────┬──────────────┬───────────────┬──────────────────────┬─────────────╮
|
|
73
|
+
│ dimension │ case │ Merge Sort │ Quick Sort │ winner │ A/B as n→∞ │ crossover n │
|
|
74
|
+
├─────────────┼───────────┼──────────────┼──────────────┼───────────────┼──────────────────────┼─────────────┤
|
|
75
|
+
│ time │ best │ O(n log n) │ O(n log n) │ tie │ equal │ — │
|
|
76
|
+
│ time │ average │ O(n log n) │ O(n log n) │ tie │ equal │ — │
|
|
77
|
+
│ time │ worst │ O(n log n) │ O(n^2) │ ◀ Merge Sort │ A/B → 0 (≈ n^0.94) │ — │
|
|
78
|
+
│ space │ best │ O(n) │ O(log n) │ Quick Sort ▶ │ A/B → ∞ (≈ n^1.00) │ — │
|
|
79
|
+
│ space │ average │ O(n) │ O(log n) │ Quick Sort ▶ │ A/B → ∞ (≈ n^1.00) │ — │
|
|
80
|
+
│ space │ worst │ O(n) │ O(n) │ tie │ equal │ — │
|
|
81
|
+
╰─────────────┴───────────┴──────────────┴──────────────┴───────────────┴──────────────────────┴─────────────╯
|
|
82
|
+
Operation-count growth (time/average case)
|
|
83
|
+
|
|
84
|
+
n Merge Sort O(n log n) Quick Sort O(n log n) A ÷ B cheaper
|
|
85
|
+
────────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
86
|
+
10 33.2 33.2 1 =
|
|
87
|
+
100 664 664 1 =
|
|
88
|
+
1,000 9.97e+03 9.97e+03 1 =
|
|
89
|
+
10,000 1.33e+05 1.33e+05 1 =
|
|
90
|
+
100,000 1.66e+06 1.66e+06 1 =
|
|
91
|
+
1,000,000 1.99e+07 1.99e+07 1 =
|
|
92
|
+
10,000,000 2.33e+08 2.33e+08 1 =
|
|
93
|
+
1,000,000,000 2.99e+10 2.99e+10 1 =
|
|
94
|
+
|
|
95
|
+
╭────────────────────────────────────────────── log-log growth ──────────────────────────────────────────────╮
|
|
96
|
+
│ 1.6e+11 │ │
|
|
97
|
+
│ │ │
|
|
98
|
+
│ XXX │
|
|
99
|
+
│ │ │
|
|
100
|
+
│ XXXXXXXX │
|
|
101
|
+
│ │ XXXXXXXX │
|
|
102
|
+
│ 5.6e+08 │ XXXXXXXX │
|
|
103
|
+
│ │ XXXXXXXX │
|
|
104
|
+
│ │ XXXXXXX │
|
|
105
|
+
│ │ XXXXXXXX │
|
|
106
|
+
│ 2.0e+06 │ XXXXXXXX │
|
|
107
|
+
│ │ XXXXXXXX │
|
|
108
|
+
│ │ XXXXXXXX │
|
|
109
|
+
│ │ XXXXXXX │
|
|
110
|
+
│ 7.2e+03 │ XXXXXXX │
|
|
111
|
+
│ │ XXXXXXXX │
|
|
112
|
+
│ │ XXXXXXX │
|
|
113
|
+
│ │ XXXXXXX │
|
|
114
|
+
│ 2.6e+01 │XX │
|
|
115
|
+
│ │ │
|
|
116
|
+
│ └─────────────────────────────────────────────────────────────────────────────────────────────── │
|
|
117
|
+
│ ───────── │
|
|
118
|
+
│ 10 100 1,000 10,000 100,000 1,000,000 10,000,000 ← n │
|
|
119
|
+
│ A = O(n log n) B = O(n log n) X = the two curves cross/coincide here │
|
|
120
|
+
│ (time/average case · both axes log-scaled · y = operations) │
|
|
121
|
+
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
122
|
+
╭──────────────────────────────────────────────── Reasoning ─────────────────────────────────────────────────╮
|
|
123
|
+
│ → time/best: Merge Sort O(n log n) vs Quick Sort O(n log n) → asymptotically equivalent (same growth │
|
|
124
|
+
│ class). │
|
|
125
|
+
│ → time/average: Merge Sort O(n log n) vs Quick Sort O(n log n) → asymptotically equivalent (same │
|
|
126
|
+
│ growth class). │
|
|
127
|
+
│ → time/worst: Merge Sort O(n log n) vs Quick Sort O(n^2) → Merge Sort is asymptotically better. the │
|
|
128
|
+
│ cost ratio grows like n^0.94, so the advantage widens without bound as n grows. │
|
|
129
|
+
│ → space/best: Merge Sort O(n) vs Quick Sort O(log n) → Quick Sort is asymptotically better. the cost │
|
|
130
|
+
│ ratio grows like n^1.00, so the advantage widens without bound as n grows. │
|
|
131
|
+
│ → space/average: Merge Sort O(n) vs Quick Sort O(log n) → Quick Sort is asymptotically better. the │
|
|
132
|
+
│ cost ratio grows like n^1.00, so the advantage widens without bound as n grows. │
|
|
133
|
+
│ → space/worst: Merge Sort O(n) vs Quick Sort O(n) → asymptotically equivalent (same growth class). │
|
|
134
|
+
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
135
|
+
╭──────────────────────────────────────────────── Conclusion ────────────────────────────────────────────────╮
|
|
136
|
+
│ Merge Sort and Quick Sort are in the same asymptotic class for average-case time (O(n log n)). Any real │
|
|
137
|
+
│ difference comes from constant factors, cache behaviour and allocation — measure before choosing. Worst │
|
|
138
|
+
│ case favours Merge Sort (O(n log n) vs O(n^2)); stability differs (Merge Sort: stable, Quick Sort: │
|
|
139
|
+
│ unstable); in-place-ness differs (Merge Sort: extra memory, Quick Sort: in-place). │
|
|
140
|
+
│ │
|
|
141
|
+
│ overall winner: Merge Sort (weighted score A=2 vs B=1.5) │
|
|
142
|
+
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Contents
|
|
148
|
+
|
|
149
|
+
- [What it does](#what-it-does)
|
|
150
|
+
- [Install / run](#install--run)
|
|
151
|
+
- [Command reference](#command-reference)
|
|
152
|
+
- [How the comparison works](#how-the-comparison-works)
|
|
153
|
+
- [Report formats](#report-formats)
|
|
154
|
+
- [Extending it](#extending-it)
|
|
155
|
+
- [Optional: empirical verification](#optional-empirical-verification)
|
|
156
|
+
- [Project layout](#project-layout)
|
|
157
|
+
- [Catalogue](#catalogue)
|
|
158
|
+
- [Design notes & limitations](#design-notes--limitations)
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## What it does
|
|
163
|
+
|
|
164
|
+
| Capability | Detail |
|
|
165
|
+
|---|---|
|
|
166
|
+
| **Case-by-case verdicts** | best / average / worst case **time** *and* **space**, never a single number |
|
|
167
|
+
| **Asymptotic winner + margin** | derived from log-log growth exponents, so `n²` vs `n log n` is classified as *strictly worse*, not "constant factor" |
|
|
168
|
+
| **Crossover detection** | finds the input size where the ordering flips (`O(√n)` really does beat `O(log n)` below n≈16) |
|
|
169
|
+
| **Constant-factor detection** | says "same growth class, ~2× apart" instead of pretending there's no difference |
|
|
170
|
+
| **Comparability checks** | warns when two entries solve different tasks or use incommensurable parameters |
|
|
171
|
+
| **Trade-off flags** | stability, in-place-ness, input-order sensitivity, extra memory |
|
|
172
|
+
| **200 algorithms, 15 families** | sorting, searching, graphs, strings, DP, number theory, crypto, data structures, linear algebra, ML, optimisation, geometry, compression, backtracking, systems |
|
|
173
|
+
| **4 report formats** | terminal (rich), Markdown, JSON, self-contained HTML with an SVG chart |
|
|
174
|
+
| **Zero dependencies** | pure standard library; rich is an optional nicety |
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Install / run
|
|
179
|
+
|
|
180
|
+
Nothing to install — run it straight from the source tree:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
cd algo-compare
|
|
184
|
+
python3 algo-compare compare merge_sort quick_sort # or ./algo-compare ...
|
|
185
|
+
python3 -m algocomp.cli compare merge_sort quick_sort # equivalent
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Optional install (gives you an `algo-compare` command anywhere):
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
git clone https://github.com/3punch/algocomp.git
|
|
192
|
+
cd algocomp
|
|
193
|
+
pip install -e . # distribution: algocomp, command: algo-compare
|
|
194
|
+
pip install -e ".[pretty]" # + rich, for nicer terminal tables
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Run the test suite:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
python3 -m unittest discover -s tests -t . -v # 57 tests
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Command reference
|
|
206
|
+
|
|
207
|
+
### `compare` — the main event
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
algo-compare compare <A> <B> [options]
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
| Option | Meaning |
|
|
214
|
+
|---|---|
|
|
215
|
+
| `--format, -f {terminal,markdown,json,html}` | report format (default `terminal`) |
|
|
216
|
+
| `--case {best,average,worst,all}` | restrict which cases are compared |
|
|
217
|
+
| `--sizes lo:hi:count` or `n1,n2,n3` | input sizes for the growth table & crossover search (default `10:1e9:8`) |
|
|
218
|
+
| `--no-space` / `--no-table` / `--no-chart` / `--no-notes` | trim the report |
|
|
219
|
+
| `--add "Name:time_avg:time_worst[:space[:category[:task]]]"` | define an algorithm inline |
|
|
220
|
+
| `--out, -o FILE` | write to a file instead of stdout |
|
|
221
|
+
| `--definitions, -D FILE` | load extra algorithms from a definition file (repeatable) |
|
|
222
|
+
|
|
223
|
+
Examples:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
# strict JSON for another tool
|
|
227
|
+
algo-compare compare dijkstra_binary_heap bellman_ford -f json
|
|
228
|
+
|
|
229
|
+
# Markdown for a PR description or README
|
|
230
|
+
algo-compare compare bubble_sort timsort -f markdown -o bubble.md
|
|
231
|
+
|
|
232
|
+
# self-contained HTML with a hand-drawn SVG chart
|
|
233
|
+
algo-compare compare jump_search binary_search -f html -o crossover.html
|
|
234
|
+
|
|
235
|
+
# compare your own algorithm against a library one
|
|
236
|
+
algo-compare compare bogosort merge_sort \
|
|
237
|
+
--add "Bogosort:factorial(n):factorial(n):1:custom:sort a list of comparable items"
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### `list`, `show`, `matrix`, `suggest`, `categories`, `benchmarks`
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
algo-compare list --category sorting # the catalogue
|
|
244
|
+
algo-compare list --search dijkstra --json
|
|
245
|
+
algo-compare show quick_sort # full profile + peers
|
|
246
|
+
algo-compare matrix --category graph --space # 28 graph algorithms at a glance
|
|
247
|
+
algo-compare matrix merge_sort quick_sort heap_sort counting_sort
|
|
248
|
+
algo-compare suggest --limit 10 # task groups with ≥2 algorithms = fair pairs
|
|
249
|
+
algo-compare categories
|
|
250
|
+
algo-compare benchmarks # which entries can be measured empirically
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### `verify` — optional empirical cross-check
|
|
254
|
+
|
|
255
|
+
Measures real runtime growth and fits `log T(n) = α + β log n`, then compares the measured
|
|
256
|
+
`β` with the declared growth exponent:
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
algo-compare verify bubble_sort --sizes 128:4096:5
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
```
|
|
263
|
+
declared worst-case : O(n^2) (growth exponent ≈ 2.000)
|
|
264
|
+
measured exponent : 1.992 (log-log least squares, R² = 1.000)
|
|
265
|
+
verdict : matches
|
|
266
|
+
|
|
267
|
+
n | seconds | ratio to prev
|
|
268
|
+
-------------+--------------+---------------
|
|
269
|
+
128 | 0.00029 | —
|
|
270
|
+
331 | 0.00198 | 6.79x
|
|
271
|
+
859 | 0.01295 | 6.53x
|
|
272
|
+
2229 | 0.08770 | 6.77x
|
|
273
|
+
4096 | 0.17501 | 2.00x
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
A doubling of `n` multiplies the time by `2^β`: ~2× for linear, ~4× for quadratic.
|
|
277
|
+
|
|
278
|
+
### `analyze`, `benchmark`, `infer` — work with your own Python files
|
|
279
|
+
|
|
280
|
+
These three commands bridge *declared* theory and *measured* reality for
|
|
281
|
+
arbitrary code. They are heuristics — confirm before quoting Big-O.
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
# 1. static estimate from source structure (loops, recursion, sort/bisect/heap)
|
|
285
|
+
algo-compare analyze my_algo.py
|
|
286
|
+
algo-compare analyze my_algo.py --json -o estimate.json
|
|
287
|
+
|
|
288
|
+
# 2. empirical timing in an isolated subprocess (time.perf_counter + tracemalloc)
|
|
289
|
+
# file must define e.g. `def main(data): ...`
|
|
290
|
+
algo-compare benchmark my_algo.py --function main --sizes 100,1000,10000 --repeats 5
|
|
291
|
+
|
|
292
|
+
# 3. both at once: static estimate + benchmark + least-squares model fit
|
|
293
|
+
algo-compare infer my_algo.py --sizes 100,1000,10000 --repeats 5
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
`benchmark` input: `--input {list,sorted,string}` builds a default `list(range(n))`
|
|
297
|
+
payload, or define `def make_input(n): ...` in the file and pass
|
|
298
|
+
`--input-factory make_input`. Each size runs in a fresh subprocess with
|
|
299
|
+
`--timeout` seconds and stops early past `--max-time`.
|
|
300
|
+
|
|
301
|
+
`infer` fits these growth models by least-squares through the origin
|
|
302
|
+
(`y = c·shape`):
|
|
303
|
+
|
|
304
|
+
| | | | |
|
|
305
|
+
|---|---|---|---|
|
|
306
|
+
| `O(1)` | `O(log n)` | `O(n)` | `O(n log log n)` |
|
|
307
|
+
| `O(n log n)` | `O(n log^2 n)` | `O(n sqrt(n))` | `O(n^2)` |
|
|
308
|
+
| `O(n^3)` | | | |
|
|
309
|
+
|
|
310
|
+
It reports `best_fit`, the full `ranking` with R², the `measured_exponent`
|
|
311
|
+
(the log-log slope of T vs n — ~1 linear, ~1.5 for `n*sqrt(n)`, ~2 quadratic),
|
|
312
|
+
and warnings on noise, close calls or too few points.
|
|
313
|
+
|
|
314
|
+
### Recognised loop shapes
|
|
315
|
+
|
|
316
|
+
The static analyzer combines loop nesting depth with the *stride* of the
|
|
317
|
+
innermost loop:
|
|
318
|
+
|
|
319
|
+
| pattern | estimate |
|
|
320
|
+
|---|---|
|
|
321
|
+
| `for j in range(n)` inside `for i in range(n)` | `O(n^2)` |
|
|
322
|
+
| `for j in range(i)` inside `for i in range(n)` | `O(n^2)` (still sweeps ~n/2) |
|
|
323
|
+
| `k = 2; for j in range(0, n, k)` inside a loop | `O(n^2)` (constant stride) |
|
|
324
|
+
| `for j in range(i*i, n+1, i)` inside a loop | `O(n log n)` — harmonic sum |
|
|
325
|
+
| same, innermost of three levels | `O(n^2 log n)` |
|
|
326
|
+
|
|
327
|
+
A stride driven by an enclosing loop (`i` here, whether the enclosing loop is a
|
|
328
|
+
`for` or a `while`) makes the inner loop walk ~`n / stride` elements, so the
|
|
329
|
+
depth-2 product collapses to `O(n log n)`. That is how a sieve of Eratosthenes
|
|
330
|
+
is reported as `O(n log n)` rather than a false `O(n^2)`. The result is flagged
|
|
331
|
+
with low confidence because the exact bound (`O(n log log n)` for a sieve) is
|
|
332
|
+
not decidable statically.
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## How the comparison works
|
|
337
|
+
|
|
338
|
+
### 1. Every bound is a real function of `n`
|
|
339
|
+
|
|
340
|
+
Complexities are stored as expressions, not labels:
|
|
341
|
+
|
|
342
|
+
```python
|
|
343
|
+
Complexity("n*log2(n)") # -> O(n log n)
|
|
344
|
+
Complexity("(V+E)*log2(V)") # -> O((V+E)·log(V))
|
|
345
|
+
Complexity("factorial(n)") # -> O(n!)
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Expressions are parsed with a validated AST (function calls, arithmetic and a whitelist of
|
|
349
|
+
variables only — no `eval`), and evaluated in log space where needed so that
|
|
350
|
+
`factorial(10⁶)` doesn't explode.
|
|
351
|
+
|
|
352
|
+
### 2. Growth exponents, not raw ratios
|
|
353
|
+
|
|
354
|
+
Each bound has a **growth exponent** — the slope of `log g(n)` vs `log n` measured between
|
|
355
|
+
`n = 10⁴` and `10¹²`:
|
|
356
|
+
|
|
357
|
+
| bound | exponent | reading |
|
|
358
|
+
|---|---|---|
|
|
359
|
+
| `O(1)` | 0.00 | flat |
|
|
360
|
+
| `O(log n)` | 0.00 | Θ(n⁰) — special-cased, since the finite-span slope of a log is ~0.06 |
|
|
361
|
+
| `O(√n)` | 0.50 | square-root |
|
|
362
|
+
| `O(n)` | 1.00 | linear |
|
|
363
|
+
| `O(n log n)` | 1.06 | linearithmic |
|
|
364
|
+
| `O(n²)` | 2.00 | quadratic |
|
|
365
|
+
| `O(2ⁿ)`, `O(n!)` | ∞ | explosive |
|
|
366
|
+
|
|
367
|
+
Two bounds are compared by their **exponent difference**, with the raw ratio used only as
|
|
368
|
+
supporting evidence. This is what makes the tool robust: a float64 ratio underflows to `0`
|
|
369
|
+
or saturates long before `n → ∞`, so a naive `gA(n)/gB(n)` would call `n²` vs `n log n` a
|
|
370
|
+
"constant factor" at any finite probe.
|
|
371
|
+
|
|
372
|
+
### 3. Dominance classification
|
|
373
|
+
|
|
374
|
+
Every case lands in exactly one bucket:
|
|
375
|
+
|
|
376
|
+
| Dominance | Meaning | Example |
|
|
377
|
+
|---|---|---|
|
|
378
|
+
| `strict` | one side wins at every realistic size, gap widens without bound | `O(n²)` vs `O(n log n)` |
|
|
379
|
+
| `constant` | same growth class, a constant factor apart | Kruskal vs Prim (both ≈ n log n) |
|
|
380
|
+
| `equal` | identical class *and* identical leading constant | merge sort vs Timsort |
|
|
381
|
+
| `crossover` | the ordering flips at some finite `n` | `O(√n)` beats `O(log n)` below n≈16 |
|
|
382
|
+
| `incomparable` | different tasks / different parameter meaning | sort vs search |
|
|
383
|
+
|
|
384
|
+
A slow-drifting log factor is caught even when the exponents are within tolerance: a
|
|
385
|
+
five-decade monotone drift of the ratio (the signature of `log n`) is upgraded from
|
|
386
|
+
`constant` to `strict`.
|
|
387
|
+
|
|
388
|
+
### 4. Crossovers
|
|
389
|
+
|
|
390
|
+
For `crossover` cases the tool bisects on `gA(n) − gB(n)` to find the flip point, and
|
|
391
|
+
suppresses crossings below `n = 8` as artefacts of dropping constant factors (where they
|
|
392
|
+
exist, the report says so rather than quoting a meaningless number).
|
|
393
|
+
|
|
394
|
+
### 5. Comparability
|
|
395
|
+
|
|
396
|
+
Comparing things that aren't comparable is a common way to get a confidently wrong answer,
|
|
397
|
+
so the tool pushes back:
|
|
398
|
+
|
|
399
|
+
- different `task` strings → **"Not strictly comparable"** banner listing why;
|
|
400
|
+
- bounds expressed in different parameter sets (`n` vs `V,E`) → warning that numeric
|
|
401
|
+
samples assume all secondary parameters equal `n`, so read them as shapes, not values;
|
|
402
|
+
- a case declared on only one side → that case is skipped, with a warning instead of a
|
|
403
|
+
silent gap.
|
|
404
|
+
|
|
405
|
+
### 6. Overall winner
|
|
406
|
+
|
|
407
|
+
A weighted score across the cases (average time ×3, worst time ×2, worst space ×1.5,
|
|
408
|
+
average space ×1, best time ×0.5) picks a single winner, and `conclusion()` writes the
|
|
409
|
+
2–4 sentence summary you see in the report.
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
## Report formats
|
|
414
|
+
|
|
415
|
+
| Format | Use it for |
|
|
416
|
+
|---|---|
|
|
417
|
+
| `terminal` | reading now, with colours, tables and an ASCII log-log chart |
|
|
418
|
+
| `markdown` | pasting into a README, PR, issue or notebook |
|
|
419
|
+
| `json` | driving another program (`winner`, `dominance`, `crossover_n`, per-case samples, …) |
|
|
420
|
+
| `html` | sharing: one file, no external assets, inline SVG chart, dark theme |
|
|
421
|
+
|
|
422
|
+
Pre-generated samples live in [`reports/`](reports/) (open `reports/index.html`):
|
|
423
|
+
|
|
424
|
+
- `sorting-merge-vs-quick` — same Big-O, different trade-offs
|
|
425
|
+
- `sorting-bubble-vs-timsort` — quadratic vs linearithmic
|
|
426
|
+
- `search-jump-vs-binary` — a genuine crossover
|
|
427
|
+
- `graph-dijkstra-vs-bellman` — asymptotics with different parameters
|
|
428
|
+
- `dp-fib-naive-vs-memo` — exponential vs linear
|
|
429
|
+
- plus complexity matrices for sorting, graphs, data structures and DP
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## Extending it
|
|
434
|
+
|
|
435
|
+
### Inline, from the command line
|
|
436
|
+
|
|
437
|
+
```bash
|
|
438
|
+
algo-compare compare "My Sorter" merge_sort \
|
|
439
|
+
--add "My Sorter:n*log2(n):n**2:n:custom:sort a list of comparable items"
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
Spec shape: `Name:time_average:time_worst[:space_worst[:category[:task]]]`.
|
|
443
|
+
Comparable only if the `task` matches the other side's.
|
|
444
|
+
|
|
445
|
+
### Definition files (best for a project's own algorithms)
|
|
446
|
+
|
|
447
|
+
```ini
|
|
448
|
+
# my_algorithms.defs
|
|
449
|
+
[hybrid_sort]
|
|
450
|
+
name = Hybrid Sort (insertion + merge)
|
|
451
|
+
category = sorting
|
|
452
|
+
task = sort a list of comparable items
|
|
453
|
+
time_worst = n*log2(n)
|
|
454
|
+
time_average = n*log2(n)
|
|
455
|
+
time_best = n
|
|
456
|
+
space_worst = n
|
|
457
|
+
stable = true
|
|
458
|
+
in_place = false
|
|
459
|
+
notes = Insertion sort below 32 elements, merge sort above.
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
```bash
|
|
463
|
+
algo-compare -D examples/my_algorithms.defs list --category custom
|
|
464
|
+
algo-compare -D examples/my_algorithms.defs compare hybrid_sort timsort
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
Allowed syntax in expressions: `+ - * / // % **`, parentheses, variables (`n`, `m`, `k`,
|
|
468
|
+
`V`, `E`, `W`, `d`, …), and `log2`, `log`, `ln`, `log10`, `sqrt`, `cbrt`, `exp`,
|
|
469
|
+
`factorial`, `perm`, `comb`, `abs`, `min`, `max`, `floor`, `ceil`, plus `pi`, `e`, `phi`.
|
|
470
|
+
Labels like `O(n log n)`, `n^2` and `2n` are accepted too.
|
|
471
|
+
|
|
472
|
+
### From Python
|
|
473
|
+
|
|
474
|
+
```python
|
|
475
|
+
from algocomp import compare, registry
|
|
476
|
+
from algocomp.algorithm import Algorithm
|
|
477
|
+
|
|
478
|
+
reg = registry()
|
|
479
|
+
verdict = compare(reg.get("merge_sort"), reg.get("quick_sort"))
|
|
480
|
+
|
|
481
|
+
print(verdict.conclusion())
|
|
482
|
+
winner, why = verdict.overall_winner
|
|
483
|
+
for case in verdict.cases:
|
|
484
|
+
print(case.explain("Merge Sort", "Quick Sort"))
|
|
485
|
+
|
|
486
|
+
# your own algorithm
|
|
487
|
+
mine = Algorithm.build(
|
|
488
|
+
"My Sort", time_worst="n*log2(n)", time_average="n*log2(n)",
|
|
489
|
+
space_worst="1", category="custom", task="sort a list of comparable items",
|
|
490
|
+
)
|
|
491
|
+
print(compare(mine, reg.get("quick_sort")).conclusion())
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
---
|
|
495
|
+
|
|
496
|
+
## Optional: empirical verification
|
|
497
|
+
|
|
498
|
+
Theory and wall-clock time disagree for good reasons (constant factors, cache behaviour,
|
|
499
|
+
allocation). `verify` exists to keep the catalogue honest — it ships pure-Python
|
|
500
|
+
benchmarks for 20 entries and reports the measured exponent next to the declared one.
|
|
501
|
+
|
|
502
|
+
```bash
|
|
503
|
+
algo-compare benchmarks
|
|
504
|
+
algo-compare verify quick_sort --sizes 512:16384:6 --budget 0.5
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
Each size is capped by `--budget` seconds, so quadratic and exponential entries stop early
|
|
508
|
+
instead of hanging the tool.
|
|
509
|
+
|
|
510
|
+
---
|
|
511
|
+
|
|
512
|
+
## Project layout
|
|
513
|
+
|
|
514
|
+
```
|
|
515
|
+
algo-compare/
|
|
516
|
+
├── algo-compare # standalone launcher (no install needed)
|
|
517
|
+
├── algocomp/
|
|
518
|
+
│ ├── expr.py # safe expression parser/evaluator (AST whitelist)
|
|
519
|
+
│ ├── complexity.py # Complexity: expression + label + rank + growth exponent
|
|
520
|
+
│ ├── algorithm.py # Algorithm & ComplexityProfile records
|
|
521
|
+
│ ├── catalog.py # 200 algorithms with best/avg/worst time & space
|
|
522
|
+
│ ├── registry.py # lookup, fuzzy search, custom definition files
|
|
523
|
+
│ ├── comparator.py # the verdict engine (dominance, crossovers, warnings)
|
|
524
|
+
│ ├── matrix.py # multi-algorithm comparison grids
|
|
525
|
+
│ ├── reports.py # terminal / Markdown / JSON / HTML renderers
|
|
526
|
+
│ ├── verify.py # optional empirical benchmark + curve fitting
|
|
527
|
+
│ └── cli.py # argument parsing and commands
|
|
528
|
+
├── examples/
|
|
529
|
+
│ ├── my_algorithms.defs # sample custom definitions
|
|
530
|
+
│ └── generate_reports.py # regenerates reports/
|
|
531
|
+
├── reports/ # pre-generated sample reports (HTML/MD/txt)
|
|
532
|
+
├── tests/test_algocomp.py # 57 unit tests
|
|
533
|
+
├── pyproject.toml
|
|
534
|
+
└── Makefile # make test | demo | reports | matrix
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
---
|
|
538
|
+
|
|
539
|
+
## Catalogue
|
|
540
|
+
|
|
541
|
+
200 algorithms across 15 families. Every entry carries best/average/worst **time** and
|
|
542
|
+
**space**, plus stability, in-place-ness and a note about constant factors and edge cases.
|
|
543
|
+
|
|
544
|
+
| Family | Examples |
|
|
545
|
+
|---|---|
|
|
546
|
+
| Sorting (13) | bubble, insertion, selection, shell, merge, quick, heap, introsort, Timsort, counting, bucket, radix, cycle |
|
|
547
|
+
| Search / retrieval (13) | linear, binary, jump, interpolation, exponential, Fibonacci, hash lookup, BFS/DFS search, quickselect, median-of-medians |
|
|
548
|
+
| Graphs (28) | BFS/DFS, Dijkstra (binary heap & Fibonacci heap), Bellman-Ford, SPFA, Floyd-Warshall, Johnson, A\*, bidirectional Dijkstra, Kruskal, Prim, Borůvka, topological sort, Tarjan, Kosaraju, bridges, bipartite check, Hierholzer, Hamiltonian path, Held-Karp, Edmonds-Karp, Dinic, push-relabel, Hungarian, Hopcroft-Karp, union-find |
|
|
549
|
+
| Strings (12) | naive, KMP, Rabin-Karp, Boyer-Moore, Z-algorithm, Aho-Corasick, suffix array, suffix automaton, backtracking regex, Thompson NFA, LCS |
|
|
550
|
+
| Dynamic programming (14) | Fibonacci (naive/memo), matrix chain, knapsack (0/1 and unbounded), subset sum, edit distance, LIS (O(n²) and O(n log n)), coin change, rod cutting, optimal BST, LPS, bitmask DP |
|
|
551
|
+
| Number theory (13) | Euclid, binary GCD, extended Euclid, trial division, Pollard's rho, Eratosthenes, linear sieve, segmented sieve, Miller-Rabin, AKS, fast modexp, Karatsuba, FFT multiplication |
|
|
552
|
+
| Crypto & hashing (10) | SHA-256, HMAC, AES, RSA, Diffie-Hellman, ECC scalar mult, PBKDF2, Argon2id, open addressing, separate chaining |
|
|
553
|
+
| Data structures (15) | dynamic array, singly/doubly linked list, binary heap, AVL, red-black, splay, B-tree, skip list, trie, segment tree, Fenwick, sparse table, sqrt decomposition, hash map |
|
|
554
|
+
| Linear algebra (15) | schoolbook/Strassen/Coppersmith-Winograd matmul, Gaussian elimination, LU, QR, Cholesky, inversion, determinant, QR iteration, SVD, FFT, convolution, conjugate gradient, Gauss-Seidel |
|
|
555
|
+
| Machine learning (17) | linear regression (normal equation & GD), logistic regression, k-means, k-NN (brute force & KD-tree), CART, random forest, gradient boosting, SVM, PCA, randomised SVD, naive Bayes, forward pass, SGD training, self-attention, beam search |
|
|
556
|
+
| Optimisation (12) | GD, GD+momentum, Adam, Newton, BFGS, Nelder-Mead, simulated annealing, genetic algorithm, simplex, branch & bound, tabu search, ACO |
|
|
557
|
+
| Computational geometry (10) | Graham scan, Jarvis march, divide & conquer hull, Chan's algorithm, closest pair, Bentley-Ottmann, Delaunay, polygon triangulation, Fortune's algorithm, point in polygon |
|
|
558
|
+
| Compression (7) | Huffman, arithmetic coding, RLE, LZ77/DEFLATE, LZW, BWT, Base64 |
|
|
559
|
+
| Backtracking & puzzles (9) | N-Queens, Sudoku, permutations, subsets, maze BFS/DFS, IDDFS, bidirectional search, meet in the middle |
|
|
560
|
+
| Databases & systems (12) | nested loop, block nested loop, hash join, sort-merge join, index nested loop, B-tree scan, full scan, external merge sort, Raft, Paxos, mark-sweep GC, generational GC |
|
|
561
|
+
|
|
562
|
+
---
|
|
563
|
+
|
|
564
|
+
## Design notes & limitations
|
|
565
|
+
|
|
566
|
+
**What the numbers mean.** The growth tables list *abstract operation counts* from the
|
|
567
|
+
declared formulas — they are not seconds, and they deliberately exclude constant factors,
|
|
568
|
+
cache behaviour and allocation cost. The tool says so in every report.
|
|
569
|
+
|
|
570
|
+
**Why everything is symbolically evaluated.** Storing `n²` as "O(n²)" throws away the
|
|
571
|
+
ability to compute, plot or compare. Storing it as an expression lets the same data drive
|
|
572
|
+
the growth table, the crossover search, the JSON output and the charts.
|
|
573
|
+
|
|
574
|
+
**Known approximations.**
|
|
575
|
+
|
|
576
|
+
- Bounds with secondary parameters (`O(n+k)`, `O(VE)`, `O(b^d)`) are sampled with every
|
|
577
|
+
non-`n` parameter set to `n`. The tool warns about this and treats those cells as shape
|
|
578
|
+
comparisons; the *classification* (strict/constant/crossover) still comes from the
|
|
579
|
+
asymptotic order, which is parameter-agnostic.
|
|
580
|
+
- `rank` is a display heuristic for sorting entries in the matrix view; the verdict engine
|
|
581
|
+
never uses it.
|
|
582
|
+
- Amortised bounds are recorded at their amortised value, with the per-operation worst case
|
|
583
|
+
(e.g. dynamic-array growth) noted in the entry's notes.
|
|
584
|
+
- Constants inside expressions are treated as given; the tool will not tell you that your
|
|
585
|
+
`O(n)` has a 10⁶ loop count.
|
|
586
|
+
|
|
587
|
+
**Verifying rather than trusting.** `verify` closes the loop for 20 entries; for the rest,
|
|
588
|
+
the theoretical bound is what a textbook says, and the report's caveat notes say when the
|
|
589
|
+
constant factor is the real story.
|
|
590
|
+
|
|
591
|
+
---
|
|
592
|
+
|
|
593
|
+
## License
|
|
594
|
+
|
|
595
|
+
MIT — use it, fork it, extend the catalogue.
|