Skip to content

Commit 1fe589e

Browse files
Merge pull request #48 from marius-bughiu/feature/celerity-dictionary-enumeration
Add struct enumerators and Keys/Values to CelerityDictionary
2 parents 9ce09b7 + 0fda8c2 commit 1fe589e

5 files changed

Lines changed: 653 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ All notable changes to Celerity are documented here. This project follows [Keep
66

77
### Added
88

9-
- `IntDictionary.GetEnumerator()`, `IntDictionary.Keys`, and `IntDictionary.Values` — struct-based, allocation-free enumeration over an `IntDictionary<TValue, THasher>`. `Keys` and `Values` expose `KeyCollection` / `ValueCollection` readonly structs, each with their own struct enumerator, so `foreach (var kvp in map)` / `foreach (int k in map.Keys)` / `foreach (var v in map.Values)` do not box. The out-of-band zero-key entry is yielded first. The enumerators track a `_version` counter and throw `InvalidOperationException` on `MoveNext` / `Reset` if the dictionary is mutated mid-enumeration, matching BCL `Dictionary<,>` semantics. First step toward implementing `IReadOnlyDictionary<int, TValue>` (#10). `CelerityDictionary` will get the equivalent surface in a follow-up.
9+
- `CelerityDictionary.GetEnumerator()`, `CelerityDictionary.Keys`, and `CelerityDictionary.Values` — struct-based, allocation-free enumeration over a `CelerityDictionary<TKey, TValue, THasher>`, mirroring the `IntDictionary` surface added earlier in 1.1.0. `Keys` and `Values` expose `KeyCollection` / `ValueCollection` readonly structs, each with their own struct enumerator, so `foreach (var kvp in map)` / `foreach (var k in map.Keys)` / `foreach (var v in map.Values)` do not box. The out-of-band default-key entry is yielded first — including `null` for reference-type keys. The enumerators track a `_version` counter and throw `InvalidOperationException` on `MoveNext` / `Reset` if the dictionary is mutated mid-enumeration, matching BCL `Dictionary<,>` semantics. Completes issue #10 and unblocks `IReadOnlyDictionary<TKey, TValue>` (#9).
10+
- `CelerityDictionaryEnumerationTests` — mirror of `IntDictionaryEnumerationTests` covering empty / single / many-entry enumeration, default-key-first ordering for both value-type and reference-type (`null`) keys, `Remove` / `Clear` / resize survival, mutation-during-enumeration detection on insert / overwrite / default-key-insert / remove / clear, `Reset` reuse, `Keys.Count` / `Values.Count` tracking, and `IEnumerable<T>` interface parity.
11+
- `IntDictionary.GetEnumerator()`, `IntDictionary.Keys`, and `IntDictionary.Values` — struct-based, allocation-free enumeration over an `IntDictionary<TValue, THasher>`. `Keys` and `Values` expose `KeyCollection` / `ValueCollection` readonly structs, each with their own struct enumerator, so `foreach (var kvp in map)` / `foreach (int k in map.Keys)` / `foreach (var v in map.Values)` do not box. The out-of-band zero-key entry is yielded first. The enumerators track a `_version` counter and throw `InvalidOperationException` on `MoveNext` / `Reset` if the dictionary is mutated mid-enumeration, matching BCL `Dictionary<,>` semantics. First step toward implementing `IReadOnlyDictionary<int, TValue>` (#10).
1012
- `Int32Murmur3Hasher` in `Celerity.Hashing` — Murmur3 32-bit finalizer ("fmix32") for `int` keys. Struct hasher, `AggressiveInlining`. Provides excellent avalanche properties; prefer over `Int32WangNaiveHasher` when key distribution is clustered or adversarial. Maps `0 → 0` (fixed point of fmix32).
1113
- `Int64WangHasher` in `Celerity.Hashing` — Thomas Wang 64-bit integer hash for `long` keys. Struct hasher, `AggressiveInlining`. Faster than `Int64Murmur3Hasher` while providing better avalanche than a simple XOR-fold; prefer when throughput matters more than adversarial collision resistance. Invertible (bijective on `ulong`) so truncation to 32 bits is the only source of collisions.
1214
- `Int32Murmur3HasherTests` — exact anchor values for key extremes, determinism, high-bit avalanche check, 1000-value distinctness sweep, and integration tests driving `CelerityDictionary` and `CeleritySet` including the `default(int)` out-of-band slot.

ISSUES.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ Both constructors now throw `ArgumentOutOfRangeException` for `capacity < 0`, `l
210210
## Backlog (post-0.2.0)
211211

212212
- **#9** — Implement `IReadOnlyDictionary<TKey, TValue>` (1.1.0). Requires `Keys`, `Values`, and `GetEnumerator()` first.
213-
- **#10** — Add `Keys` / `Values` / `GetEnumerator()` (1.1.0). Status: `in-progress in 1.1.0``IntDictionary` done (allocation-free struct enumerators, zero-key is yielded first, BCL-style mid-enumeration mutation detection). `CelerityDictionary` still to do.
213+
- **#10** — Add `Keys` / `Values` / `GetEnumerator()` (1.1.0). Status: `fixed in 1.1.0`both `IntDictionary` and `CelerityDictionary` now ship allocation-free struct enumerators with `KeyCollection` / `ValueCollection` views. The out-of-band default-key entry (zero-key for `IntDictionary`, `default(TKey)` for `CelerityDictionary`, including `null` for reference-type keys) is yielded first. Mutating the dictionary mid-enumeration throws `InvalidOperationException` on the next `MoveNext` / `Reset`, enforced by a per-instance `_version` counter. Implementing `IReadOnlyDictionary<TKey, TValue>` on top of these is now unblocked (#9).
214214
- **#11** — Add `Add` / `TryAdd` with duplicate-throwing semantics (0.3.0). Status: `fixed in 0.3.0`.
215215
- **#12**`Int32Murmur3Hasher`, `Int64WangHasher`, `GuidHasher`, `UInt32Hasher`, `UInt64Hasher` (0.4.0). Status: `fixed in 1.1.0``UInt32Hasher`, `UInt64Hasher`, `GuidHasher`, `Int32Murmur3Hasher`, and `Int64WangHasher` all complete.
216216
- **#13**`DefaultHasher<T>` fallback to `EqualityComparer<T>.Default.GetHashCode()`. Status: `fixed in 1.1.0`.

docs/api/collections.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ Creates a new dictionary. `capacity` is rounded up to the next power of two. `lo
3535
| Property | Type | Description |
3636
|----------|------|-------------|
3737
| `Count` | `int` | Number of key/value pairs in the dictionary. |
38+
| `Keys` | `KeyCollection` | Allocation-free enumerable view over the keys. |
39+
| `Values` | `ValueCollection` | Allocation-free enumerable view over the values. |
3840

3941
### Indexer
4042

@@ -94,6 +96,14 @@ public void Clear()
9496

9597
Removes all entries. The underlying array capacity is preserved.
9698

99+
#### GetEnumerator
100+
101+
```csharp
102+
public Enumerator GetEnumerator()
103+
```
104+
105+
Returns a struct enumerator that yields `KeyValuePair<TKey, TValue?>`. The out-of-band default-key entry is yielded first if present. Mutating the dictionary during enumeration throws `InvalidOperationException` from the next `MoveNext` / `Reset` call, matching BCL `Dictionary<,>` semantics. Iteration order is unspecified and may change between versions.
106+
97107
### Default-key handling
98108

99109
`default(TKey)` (which is `null` for reference types, `0` for `int`, `Guid.Empty` for `Guid`, etc.) cannot be stored in the regular probe table because it doubles as the empty-slot sentinel. Celerity handles this transparently via a dedicated `_hasDefaultKey` flag and a separate value slot, so callers never need to worry about it.
@@ -110,6 +120,13 @@ dict[0] = "zero is fine";
110120

111121
if (dict.TryGetValue(42, out var val))
112122
Console.WriteLine(val); // "hello"
123+
124+
// Zero-allocation enumeration (struct enumerator):
125+
foreach (var kvp in dict)
126+
Console.WriteLine($"{kvp.Key} -> {kvp.Value}");
127+
128+
foreach (int key in dict.Keys) { /* ... */ }
129+
foreach (var value in dict.Values) { /* ... */ }
113130
```
114131

115132
---

0 commit comments

Comments
 (0)