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.
@@ -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
+ ![tests](https://github.com/3punch/algocomp/actions/workflows/tests.yml/badge.svg)
38
+ ![python](https://img.shields.io/badge/python-3.10%2B-blue)
39
+ ![dependencies](https://img.shields.io/badge/dependencies-none-brightgreen)
40
+ ![license](https://img.shields.io/badge/license-MIT-green)
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.