@@ -44,6 +44,9 @@ public sealed class SortedKvStore : IDisposable, IEnumerable<KeyValuePair<int, i
4444 /// </summary>
4545 private readonly UnmanagedIntArray _values ;
4646
47+ private UnmanagedIntList _activeIndicesCache ;
48+ private bool _activeIndicesDirty = true ;
49+
4750 /// <summary>
4851 /// Initializes a new instance of the <see cref="SortedKvStore" /> class with a specified initial capacity.
4952 /// </summary>
@@ -53,20 +56,21 @@ public SortedKvStore(int initialCapacity = 16)
5356 _keys = new UnmanagedIntArray ( initialCapacity ) ;
5457 _values = new UnmanagedIntArray ( initialCapacity ) ;
5558 _occupied = new UnmanagedIntArray ( initialCapacity ) ;
59+ _activeIndicesCache = new UnmanagedIntList ( initialCapacity ) ;
5660 }
5761
5862 /// <summary>
5963 /// Gets the number of active (occupied) key-value pairs stored.
6064 /// </summary>
61- public int Count { get ; private set ; }
65+ public int OccupiedCount { get ; private set ; }
6266
6367 /// <summary>
6468 /// Gets the free capacity.
6569 /// </summary>
6670 /// <value>
6771 /// The free capacity.
6872 /// </value>
69- public int FreeCapacity => _keys . Capacity - Count ;
73+ public int FreeCapacity => _keys . Capacity - OccupiedCount ;
7074
7175 /// <summary>
7276 /// Gets an enumerable collection of all keys currently in the store.
@@ -78,7 +82,7 @@ public IEnumerable<int> Keys
7882 {
7983 get
8084 {
81- for ( var i = 0 ; i < Count ; i ++ )
85+ for ( var i = 0 ; i < OccupiedCount ; i ++ )
8286 {
8387 if ( _occupied [ i ] != 0 )
8488 {
@@ -124,6 +128,7 @@ public void Dispose()
124128 _keys . Dispose ( ) ;
125129 _values . Dispose ( ) ;
126130 _occupied . Dispose ( ) ;
131+ _activeIndicesCache . Dispose ( ) ;
127132 }
128133
129134 /// <inheritdoc />
@@ -133,12 +138,9 @@ public void Dispose()
133138 /// <returns>All active elements as Key Value pair.</returns>
134139 public IEnumerator < KeyValuePair < int , int > > GetEnumerator ( )
135140 {
136- for ( var i = 0 ; i < Count ; i ++ )
141+ foreach ( var idx in GetActiveIndices ( ) )
137142 {
138- if ( _occupied [ i ] != 0 )
139- {
140- yield return new KeyValuePair < int , int > ( _keys [ i ] , _values [ i ] ) ;
141- }
143+ yield return new KeyValuePair < int , int > ( _keys [ idx ] , _values [ idx ] ) ;
142144 }
143145 }
144146
@@ -167,25 +169,28 @@ public void Add(int key, int value)
167169 {
168170 _occupied [ idx ] = 1 ;
169171 _values [ idx ] = value ;
170- Count ++ ;
172+ OccupiedCount ++ ;
171173 }
172174 else
173175 {
174- _values [ idx ] = value ;
176+ _values [ idx ] = value ; // update
175177 }
176178
177179 return ;
178180 }
179181
180182 idx = ~ idx ;
181183
184+ // Insert at physical index, so check raw capacity not OccupiedCount
182185 EnsureCapacity ( ) ;
183186
184187 _keys . InsertAt ( idx , key ) ;
185188 _values . InsertAt ( idx , value ) ;
186189 _occupied . InsertAt ( idx , 1 ) ;
187190
188- Count ++ ;
191+ OccupiedCount ++ ;
192+
193+ _activeIndicesDirty = true ;
189194 }
190195
191196 /// <summary>
@@ -212,10 +217,10 @@ public bool ContainsKey(int key)
212217 /// <returns><c>true</c> if the key was found; otherwise, <c>false</c>.</returns>
213218 public bool TryGetValue ( int key , out int value )
214219 {
215- int left = 0 , right = Count - 1 ;
216- var keysSpan = _keys . AsSpan ( ) [ ..Count ] ;
217- var occupiedSpan = _occupied . AsSpan ( ) [ ..Count ] ;
218- var valuesSpan = _values . AsSpan ( ) [ ..Count ] ;
220+ int left = 0 , right = OccupiedCount - 1 ;
221+ var keysSpan = _keys . AsSpan ( ) [ .._keys . Length ] ;
222+ var occupiedSpan = _occupied . AsSpan ( ) [ ..OccupiedCount ] ;
223+ var valuesSpan = _values . AsSpan ( ) [ ..OccupiedCount ] ;
219224
220225 while ( left <= right )
221226 {
@@ -272,10 +277,12 @@ public bool TryRemove(int key, out int index)
272277 if ( index >= 0 && _occupied [ index ] != 0 )
273278 {
274279 _occupied [ index ] = 0 ;
280+ _activeIndicesDirty = true ;
275281 return true ;
276282 }
277283
278284 index = - 1 ;
285+
279286 return false ;
280287 }
281288
@@ -291,8 +298,8 @@ public void RemoveMany(ReadOnlySpan<int> keysToRemove)
291298 return ;
292299 }
293300
294- var occSpan = _occupied . AsSpan ( ) [ ..Count ] ;
295- var keysSpan = _keys . AsSpan ( ) [ ..Count ] ;
301+ var occSpan = _occupied . AsSpan ( ) [ ..OccupiedCount ] ;
302+ var keysSpan = _keys . AsSpan ( ) [ ..OccupiedCount ] ;
296303
297304 // Optional: if keysToRemove is sorted, do a linear merge
298305 // This path is faster than HashSet-based if input is sorted and large
@@ -312,7 +319,7 @@ public void RemoveMany(ReadOnlySpan<int> keysToRemove)
312319 if ( isSorted )
313320 {
314321 int i = 0 , j = 0 ;
315- while ( i < Count && j < keysToRemove . Length )
322+ while ( i < OccupiedCount && j < keysToRemove . Length )
316323 {
317324 var currentKey = keysSpan [ i ] ;
318325 var removeKey = keysToRemove [ j ] ;
@@ -328,7 +335,7 @@ public void RemoveMany(ReadOnlySpan<int> keysToRemove)
328335 else
329336 {
330337 occSpan [ i ] = 0 ;
331-
338+ _activeIndicesDirty = true ;
332339 i ++ ;
333340 j ++ ;
334341 }
@@ -343,6 +350,7 @@ public void RemoveMany(ReadOnlySpan<int> keysToRemove)
343350 if ( idx >= 0 && occSpan [ idx ] != 0 )
344351 {
345352 occSpan [ idx ] = 0 ;
353+ _activeIndicesDirty = true ;
346354 }
347355 }
348356 }
@@ -354,13 +362,13 @@ public void RemoveMany(ReadOnlySpan<int> keysToRemove)
354362 /// </summary>
355363 public void Compact ( )
356364 {
357- var occSpan = _occupied . AsSpan ( ) [ ..Count ] ;
358- var maxRemoved = Count ; // worst case: all are removed
365+ var occSpan = _occupied . AsSpan ( ) [ ..OccupiedCount ] ;
366+ var maxRemoved = OccupiedCount ; // worst case: all are removed
359367
360368 var rented = ArrayPool < int > . Shared . Rent ( maxRemoved ) ;
361369 var removedCount = 0 ;
362370
363- for ( var i = 0 ; i < Count ; i ++ )
371+ for ( var i = 0 ; i < OccupiedCount ; i ++ )
364372 {
365373 if ( occSpan [ i ] == 0 )
366374 {
@@ -380,11 +388,17 @@ public void Compact()
380388 _values . RemoveMultiple ( indicesToRemove ) ;
381389 _occupied . RemoveMultiple ( indicesToRemove ) ;
382390
383- Count -= removedCount ;
391+ OccupiedCount -= removedCount ;
384392
385393 ArrayPool < int > . Shared . Return ( rented ) ;
386394 }
387395
396+ public Span < int > AsSpan ( )
397+ {
398+ var list = GetActiveIndices ( ) ;
399+ return list . AsSpan ( ) ;
400+ }
401+
388402 /// <summary>
389403 /// Performs a binary search for the specified key.
390404 /// </summary>
@@ -394,9 +408,13 @@ public void Compact()
394408 /// </returns>
395409 public int BinarySearch ( int key )
396410 {
397- int left = 0 , right = Count - 1 ;
398- var keysSpan = _keys . AsSpan ( ) [ ..Count ] ;
399- var occupiedSpan = _occupied . AsSpan ( ) [ ..Count ] ;
411+ //var list = GetActiveIndices();
412+ //return list.AsSpan().BinarySearch(key);
413+
414+
415+ int left = 0 , right = OccupiedCount - 1 ;
416+ var keysSpan = _keys . AsSpan ( ) [ ..OccupiedCount ] ;
417+ var occupiedSpan = _occupied . AsSpan ( ) [ ..OccupiedCount ] ;
400418
401419 while ( left <= right )
402420 {
@@ -440,7 +458,7 @@ public int BinarySearch(int key)
440458
441459 // Key not found; try to move left forward to next occupied slot for insertion index
442460 int insertionIndex = left ;
443- while ( insertionIndex < Count && occupiedSpan [ insertionIndex ] == 0 )
461+ while ( insertionIndex < OccupiedCount && occupiedSpan [ insertionIndex ] == 0 )
444462 insertionIndex ++ ;
445463
446464 return ~ insertionIndex ;
@@ -454,7 +472,9 @@ public void Clear()
454472 _keys . Clear ( ) ;
455473 _values . Clear ( ) ;
456474 _occupied . Clear ( ) ;
457- Count = 0 ;
475+ _activeIndicesCache . Clear ( ) ;
476+ _activeIndicesDirty = true ;
477+ OccupiedCount = 0 ;
458478 }
459479
460480 /// <summary>
@@ -466,9 +486,9 @@ public void Clear()
466486 public override string ToString ( )
467487 {
468488 var sb = new System . Text . StringBuilder ( ) ;
469- sb . AppendLine ( $ "Count: { Count } ") ;
489+ sb . AppendLine ( $ "Count: { OccupiedCount } ") ;
470490
471- for ( int i = 0 ; i < Count ; i ++ )
491+ for ( int i = 0 ; i < OccupiedCount ; i ++ )
472492 {
473493 sb . AppendLine ( $ "Index { i } : Key={ _keys [ i ] } , Value={ _values [ i ] } , Occupied={ _occupied [ i ] } ") ;
474494 }
@@ -490,9 +510,37 @@ public override string ToString()
490510 private void EnsureCapacity ( )
491511 {
492512 // Delegate to IntArray.EnsureCapacity, using Count + 1 since we add one item
493- _keys . EnsureCapacity ( Count + 1 ) ;
494- _values . EnsureCapacity ( Count + 1 ) ;
495- _occupied . EnsureCapacity ( Count + 1 ) ;
513+ _keys . EnsureCapacity ( OccupiedCount + 1 ) ;
514+ _values . EnsureCapacity ( OccupiedCount + 1 ) ;
515+ _occupied . EnsureCapacity ( OccupiedCount + 1 ) ;
516+ }
517+
518+ private UnmanagedIntList GetActiveIndices ( )
519+ {
520+ if ( ! _activeIndicesDirty && _activeIndicesCache != null )
521+ {
522+ return _activeIndicesCache ! ;
523+ }
524+
525+ GenerateActiveIndices ( ) ;
526+ _activeIndicesDirty = false ;
527+
528+ return _activeIndicesCache ! ;
529+ }
530+
531+ private void GenerateActiveIndices ( )
532+ {
533+ _activeIndicesCache ? . Dispose ( ) ;
534+ _activeIndicesCache = new UnmanagedIntList ( OccupiedCount ) ; // or Count if unsure
535+
536+ var occ = _occupied . AsSpan ( ) [ .._keys . Length ] ;
537+ for ( int i = 0 ; i < occ . Length ; i ++ )
538+ {
539+ if ( occ [ i ] != 0 )
540+ {
541+ _activeIndicesCache . Add ( i ) ;
542+ }
543+ }
496544 }
497545 }
498546}
0 commit comments