You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+4Lines changed: 4 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,6 +4,10 @@ All notable changes to Celerity are documented here. This project follows [Keep
4
4
5
5
## [Unreleased]
6
6
7
+
### Added
8
+
9
+
-**`BTreeDictionary<TKey, TValue, TComparer>` and `BTreeSet<T, TComparer>`** (with `BTreeDictionary<TKey, TValue>` / `BTreeSet<T>` aliases and the `DefaultComparer<T>` struct comparer) in `Celerity.Collections` — the library's first sorted map and set, and the B-tree the BCL lacks. Up to 31 keys per node keep a lookup `log₃₂(n)` node visits deep instead of chasing the `log₂(n)` pointers a red-black tree costs, and both add the ordered surface a hash table cannot answer: `Min` / `Max`, lower / upper bound, `EnumerateRange` in `O(log n + k)`, and in-order enumeration. They win on the interleaved insert + lookup + range-scan workload and on memory, and lose slightly on a delete-dominated one. Not thread-safe. Closes [#305](https://github.com/marius-bughiu/Celerity/issues/305).
-`Trie<TValue>` — ordered **prefix tree** mapping string keys to values. `GetByPrefix` lists every entry whose key starts with a prefix in `O(prefix + matches)`, and `TryGetLongestPrefix` finds the longest stored key that is a prefix of a query in `O(query)`. The trie the BCL lacks — autocomplete, longest-prefix routing, and ordered (ascending-ordinal) iteration, where a `Dictionary<string, TValue>` has no prefix index and must scan every key and run `StartsWith`. Exact `Add` / `TryGetValue` favour a `Dictionary` (one hash vs a character walk); the trie earns its place on the prefix operations. Implements `IReadOnlyDictionary<string, TValue?>`.
88
88
89
+
**Sorted (ordered) collections**
90
+
91
+
-`BTreeDictionary<TKey, TValue, TComparer>` / `BTreeDictionary<TKey, TValue>` — a **sorted map backed by a B-tree**: up to **31 keys per node** in flat arrays, so a lookup visits `log₃₂(n)` nodes instead of chasing `log₂(n)` pointers — roughly **4 cache misses instead of ~20 at `n = 1M`**. Adds the ordered surface a hash table cannot answer: `Min`, `Max`, `TryGetLowerBound` / `TryGetUpperBound`, `EnumerateRange` in `O(log n + k)`, and in-order enumeration. The B-tree the BCL lacks — `SortedDictionary<,>` is a red-black tree with one heap object per entry, and `SortedList<,>` memmoves the tail on every insert. Implements `IDictionary<TKey, TValue?>` and `IReadOnlyDictionary<TKey, TValue?>`.
92
+
-`BTreeSet<T, TComparer>` / `BTreeSet<T>` — the set counterpart, with the same ordered surface and no values to store, so the memory saving over `SortedSet<T>` is larger still. Implements `ISet<T>` and `IReadOnlySet<T>`.
93
+
94
+
Both take their ordering as a **struct**`IComparer<T>` type parameter (`DefaultComparer<T>` by default), exactly as the hashers are struct type parameters, so the comparison inlines instead of costing a virtual call per key inspected inside a node.
95
+
89
96
**Prefix sums**
90
97
91
98
-`FenwickTree<T>` — a **Binary Indexed Tree** over a fixed-length numeric sequence (`where T : struct, INumber<T>`): **point update** and **prefix / range sum** both in `O(log n)`, in one flat array with no per-node overhead. The prefix-sum structure the BCL lacks — running aggregates, rank / order-statistics counters, cumulative-frequency tables — where a plain array is `O(n)` per query (recompute the slice) *or*`O(n)` per update (fix the suffix). Wins precisely when updates and partial-sum queries interleave.
@@ -471,6 +478,31 @@ if (routes.TryGetLongestPrefix("/api/v1/users/42", out string? route, out string
471
478
472
479
</details>
473
480
481
+
<details>
482
+
<summary><b>Sorted maps and sets with range scans</b> — BTreeDictionary / BTreeSet</summary>
483
+
484
+
`BTreeDictionary<TKey, TValue>` and `BTreeSet<T>` keep their keys in order across nodes of up to 31 keys held in flat arrays, so a lookup visits `log₃₂(n)` nodes rather than chasing `log₂(n)` pointers the way `SortedDictionary<,>` / `SortedSet<>` (red-black trees, one heap object per entry) must. They add the ordered surface a hash table has no answer for — bounds and `O(log n + k)` range scans.
485
+
486
+
```csharp
487
+
varseries=newBTreeDictionary<long, double>();
488
+
series[1_000] =1.5;
489
+
series[1_010] =2.5;
490
+
series[1_020] =3.5;
491
+
492
+
Console.WriteLine(series.Min.Key); // 1000
493
+
Console.WriteLine(series.Max.Key); // 1020
494
+
495
+
// First key at or after 1005, and the first strictly after it.
<summary><b>Prefix sums with live updates</b> — FenwickTree</summary>
476
508
@@ -548,10 +580,11 @@ Each type buys a different tradeoff. Find your workload below; if it isn't here,
548
580
|**Incremental connectivity / connected components** — union equivalence classes and ask whether two elements are in the same group (Kruskal MST, clustering, image segmentation, undirected cycle detection, "are these accounts linked?") |`DisjointSet<T>`| Union-find with **union by size** + **path halving**: near-`O(1)` amortized `Union` / `Find` / `Connected`, `O(α(n)) ≤ 4`. Runs a stream of merges + connectivity queries in near-linear total time, where the BCL substitutes are super-linear — a `Dictionary<T, HashSet<T>>` set-merge is `O(n²)` to coalesce `n` singletons, and a per-query BFS/DFS is `O(V+E)` every query. Grows only by merging (no un-union); it is not an `ISet<T>` — for element membership with add/remove/set-algebra use `CeleritySet` or `HashSet<T>`. |
549
581
|**Priority queue whose priorities change** — a best-so-far frontier you relax (Dijkstra / Prim / A\*), or an event scheduler that reschedules / cancels pending items |`IndexedPriorityQueue<TElement, TPriority, THasher>`| Addressable binary min-heap with an element→slot index: `Update` (decrease-/increase-key) and `Remove` an arbitrary element in `O(log n)`, `Contains` / `TryGetPriority` in `O(1)`. The BCL `PriorityQueue<,>` can do none of these — its only substitute is lazy deletion, which grows the heap by one entry per update. Each element is a key (appears once); custom `IComparer<TPriority>` for a max-heap. For plain enqueue/dequeue with duplicate elements, the BCL `PriorityQueue<,>` is simpler. |
550
582
|**Prefix / autocomplete / longest-prefix** over string keys — list everything under a prefix, find the most specific stored key that prefixes a query, or iterate keys in order (typeahead, route/dispatch tables, tokenizer / dictionary matching, namespace listing) |`Trie<TValue>`| Ordered prefix tree: `GetByPrefix` yields every entry under a prefix in `O(prefix + matches)` and in ascending key order, `TryGetLongestPrefix` finds the longest stored prefix of a query in `O(query)`, and enumeration is sorted for free — none of which a `Dictionary<string, TValue>` can do without an `O(n)` scan + `StartsWith`. For **pure exact-key**`Add` / `TryGetValue` / `Remove` a `Dictionary` (one hash vs a per-character walk) is faster; the trie earns its place only when you use the prefix operations. Implements `IReadOnlyDictionary<string, TValue?>`; not thread-safe. |
583
+
| **Sorted keys** — you need the entries in comparer order, or the ordered questions a hash table cannot answer: smallest / largest key, "first key at or after *x*", "every key in `[a, b)`" (time-series by timestamp, order books, LSM-style memtables, sweep-line events, interval endpoints) | `BTreeDictionary<TKey, TValue>` / `BTreeSet<T>` | B-tree with up to 31 keys per node in flat arrays: a lookup visits `log₃₂(n)` nodes instead of chasing `log₂(n)` pointers (~4 cache misses instead of ~20 at `n = 1M`), an in-order walk streams contiguous arrays rather than successor pointers, and allocation is one node per 31 entries instead of one object per entry. The BCL has no B-tree: `SortedDictionary<,>` / `SortedSet<>` are red-black trees, `SortedList<,>` is `O(n)` per middle insert, and `OrderedDictionary<,>` (.NET 9) is *insertion*-ordered, not sorted. Wins on the **interleaved insert + lookup + range-scan** load; for a few dozen entries a `SortedList<,>` is hard to beat, and if you never need order a hash table answers in `O(1)`. |
551
584
|**Prefix / range sums over a sequence you keep mutating** — running aggregates, rank / order-statistics counters (inversions, "how many ≤ x seen"), cumulative-frequency tables |`FenwickTree<T>`| Binary Indexed Tree (`T : INumber<T>`): **point update** and **prefix / range sum** both `O(log n)`, in one array with no per-node overhead. The BCL has no prefix-sum structure; a plain array forces `O(n)` per query (recompute the slice) *or*`O(n)` per update (fix the suffix). Wins precisely when updates and partial-sum queries interleave. If the data is immutable after build, a one-shot precomputed prefix-sum array answers in `O(1)` with less code; if you only update and never query a partial sum, a raw array is simpler. |
552
-
| Need a stable iteration order or multi-threaded access |BCL `Dictionary<,>`, `ConcurrentDictionary<,>`(or `Trie<TValue>` for ordered string keys) | Celerity is single-threaded, and the hash-based collections leave iteration order unspecified. The exception is `Trie<TValue>`, which iterates in ascending ordinal key order by contract. |
585
+
| Need a stable iteration order or multi-threaded access |`BTreeDictionary<,>` / `BTreeSet<>`for sorted order, `Trie<TValue>` for ordered string keys; BCL `ConcurrentDictionary<,>` for concurrency | Celerity is single-threaded, and the **hash-based** collections leave iteration order unspecified. The ordered collections do promise order by contract: the B-trees iterate in comparer order, `Trie<TValue>`in ascending ordinal key order. |
553
586
554
-
**Celerity is not the right answer when** you need concurrent access (use `ConcurrentDictionary<,>` or your own lock — Celerity is single-threaded), the mutable `IDictionary<,>` interface, or a guaranteed iteration order from the **hash-based** collections (the dictionaries and sets expose `IReadOnlyDictionary<,>`/ `IReadOnlySet<>` only and do not promise order across versions). If you need ordered string-keyed iteration, `Trie<TValue>`provides it by contract (ascending ordinal key order).
587
+
**Celerity is not the right answer when** you need concurrent access (use `ConcurrentDictionary<,>` or your own lock — Celerity is single-threaded), or a guaranteed iteration order from the **hash-based** collections (those dictionaries expose `IReadOnlyDictionary<,>`and those sets `ISet<>`, and neither promises an order across versions). When you do need ordered iteration, reach for the ordered collections instead: `BTreeDictionary<,>`/ `BTreeSet<>` iterate in comparer order and support bounds and range scans, and `Trie<TValue>` gives ascending ordinal order over string keys. `BTreeDictionary<,>` also implements the **mutable**`IDictionary<,>` interface, and `BTreeSet<>` implements `ISet<>`.
Copy file name to clipboardExpand all lines: ROADMAP.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -216,9 +216,9 @@ The 2026-Q3 review surveyed the shipped surface against the current .NET 8/9/10
216
216
- Delete the per-probe virtual call. The probe loops test for an empty slot with `EqualityComparer<TKey>.Default.Equals(slot, default(TKey))`, which the JIT devirtualizes for value-type keys but not under `__Canon`-shared reference-type instantiations — one `callvirt` per probe iteration to perform what is a null check. Guiding Principle #2 exists to remove exactly this. Status: `planned`.
217
217
- Span-keyed lookups on the string-keyed collections. .NET 9's `GetAlternateLookup<ReadOnlySpan<char>>` lets the BCL `Dictionary` probe with a span key and no allocation; Celerity's string-keyed types require a materialized `string`, so the BCL is now *ahead* on the axis this library has invested most in. Status: `planned`.
218
218
219
-
**The ordered / compressed integer-data lane.**The largest structural hole left: 38 collections and not one sorted map or set, with `Trie` the only ordered type and it string-keyed.
219
+
**The ordered / compressed integer-data lane.**Opened by the sorted-container hole — 38 collections and not one sorted map or set, with `Trie` the only ordered type, and that one string-keyed. The B-trees below close that half; the compressed-integer half is still open.
220
220
221
-
-`BTreeDictionary<TKey, TValue, TComparer>` / `BTreeSet<T, TComparer>` — cache-friendly sorted containers against `SortedDictionary<,>` / `SortedSet<T>`, which are red-black trees with a pointer chase and an allocation per node. Status: `planned`.
221
+
-`BTreeDictionary<TKey, TValue, TComparer>` / `BTreeSet<T, TComparer>` — cache-friendly sorted containers against `SortedDictionary<,>` / `SortedSet<T>`, which are red-black trees with a pointer chase and an allocation per node. Status: `done`. Tracked in [#305](https://github.com/marius-bughiu/Celerity/issues/305).
222
222
-`CompressedIntSet` — a Roaring-style compressed set of 32-bit integers, covering the huge-and-sparse shape that neither `BitSet` (dense, bounded) nor `SparseSet` (small universe, `O(Universe)` memory) nor `IntSet` (hash) serves. Status: `planned`.
223
223
-`RankSelectBitVector` — succinct `Rank` / `Select` over a dense bit vector, the primitive the above compose on. Status: `planned`.
224
224
- Sorted-span set algebra in `Celerity.Primitives` — merge-based `Intersect` / `Union` / `Except` / `IntersectCount` over already-sorted spans, where the BCL answer is LINQ or a `HashSet` round-trip. Status: `planned`.
0 commit comments