Skip to content

Commit c51fb58

Browse files
marius-bughiuclaude
andcommitted
docs(SegmentTree): describe the baseline as a fold loop, not a Min() call
The baseline was written as `values.AsSpan(start, length).Min()` in the type docs, the API reference and the benchmark header. Two things wrong with it. It is min-specific, in the docs for a type whose whole point is an arbitrary associative fold. And it does not compile: `Span<T>` has no `Min` — there is no MemoryExtensions overload for it — so a reader who tried the snippet would find out the hard way. Reworded all three as what the baseline actually is: a hand-written loop that folds the slice element by element, O(n) per query whatever the fold, against which precomputing the answers instead trades the cost onto every point update. The absence of a span helper is now stated rather than assumed, and range minimum is named as the instance the benchmark measures — it is the cheapest fold that loop can carry, so it is the baseline's best case. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent d646716 commit c51fb58

3 files changed

Lines changed: 10 additions & 6 deletions

File tree

docs/api/collections.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4279,7 +4279,7 @@ A **segment tree** is a fixed-length, array-backed sequence that answers the agg
42794279

42804280
It is the half of the range-query space [`FenwickTree<T>`](#fenwicktreet) cannot reach. A Fenwick range query is the *difference* of two prefix folds, so the operation must have an inversewhich is why that type is constrained to `INumber<T>` and answers sums only. A segment tree stores each node's fold outright and never subtracts, so range **minimum**, **maximum**, **gcd**, bitwise **and**/**or** — and any fold you write yourself — are all in reach. Where sums are what you want, prefer `FenwickTree<T>`: it does the same job in half the memory.
42814281
4282-
The BCL has no range-aggregate structure at all, so the baseline is a plain `T[]` and a scan — `values.AsSpan(start, length).Min()` is `O(n)` per query, and precomputing the answers is `O(n)` per update.
4282+
The BCL has no range-aggregate structure at all, so the baseline is a plain `T[]` and a loop that folds the slice element by element — `O(n)` per query, whatever the fold — while precomputing the answers instead makes every point update `O(n)`. There is not even a span helper to lean on: `Span<T>` has no `Min` or `Max`, let alone an arbitrary combine, so the loop is written out by hand. (The benchmark measures the range-**minimum** instance of it, which is the cheapest per element the baseline gets.)
42834283
42844284
### The fold: `IMonoid<T>`
42854285

src/Celerity.Benchmarks/SegmentTreeBenchmark.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44

55
// SegmentTree<long, MinMonoid<long>> vs the plain-array baseline, on the fold FenwickTree structurally cannot
66
// answer. A Fenwick range query is the difference of two prefix folds, so it needs an inverse; minimum has
7-
// none. The BCL ships no range-aggregate structure at all, so the honest baseline is a raw long[] and a scan:
8-
// values.AsSpan(start, length).Min() is O(n) per query, and precomputing the answers is O(n) per update.
7+
// none. The BCL ships no range-aggregate structure at all — not even a Span<T>.Min to lean on — so the honest
8+
// baseline is a raw long[] and a hand-written loop folding the slice: O(n) per query, while precomputing the
9+
// answers instead would make every point update O(n). Range minimum is the cheapest fold that loop can carry,
10+
// which makes it the baseline's best case and the fair one to measure against.
911
//
1012
// Two categories cover the documented BCL-beating shape. Mixed interleaves point updates with range-minimum
1113
// queries (the headline workload: sliding-window minima over a mutating history, "cheapest offer in this

src/Celerity/Collections/SegmentTree.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@ namespace Celerity.Collections;
2222
/// prefer <see cref="FenwickTree{T}"/> — it does the same job in half the memory.
2323
/// </para>
2424
/// <para>
25-
/// The BCL has no range-aggregate structure at all, so the baseline is a plain <c>T[]</c> and a scan:
26-
/// <c>values.AsSpan(start, length).Min()</c> is <c>O(n)</c> per query, and precomputing the answers is
27-
/// <c>O(n)</c> per update. The tree gives <b>both</b> in <c>O(log n)</c>, so it wins precisely when updates and
25+
/// The BCL has no range-aggregate structure at all, so the baseline is a plain <c>T[]</c> and a loop that folds
26+
/// the slice element by element — <c>O(n)</c> per query, whatever the fold — while precomputing the answers
27+
/// instead makes every point update <c>O(n)</c>. There is not even a span helper to lean on: <c>Span&lt;T&gt;</c>
28+
/// has no <c>Min</c> or <c>Max</c>, let alone an arbitrary combine, so the loop is written out by hand.
29+
/// The tree gives <b>both</b> in <c>O(log n)</c>, so it wins precisely when updates and
2830
/// range queries interleave — sliding-window minima and maxima over a mutating history, per-window
2931
/// capability masks, "cheapest offer in this price band" over a live order book, and the same rank / windowed
3032
/// aggregate shapes <see cref="FenwickTree{T}"/> serves for sums.

0 commit comments

Comments
 (0)