< Summary

Information
Class: SwiftCollections.SwiftSparseMap<T>
Assembly: SwiftCollections
File(s): /home/runner/work/SwiftCollections/SwiftCollections/src/SwiftCollections/Collection/SwiftSparseMap.cs
Line coverage
100%
Covered lines: 218
Uncovered lines: 0
Coverable lines: 218
Total lines: 745
Line coverage: 100%
Branch coverage
100%
Covered branches: 80
Total branches: 80
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%11100%
.ctor(...)100%1010100%
.ctor(...)100%11100%
get_Count()100%11100%
get_DenseCapacity()100%11100%
get_SparseCapacity()100%11100%
get_IsSynchronized()100%11100%
get_SyncRoot()100%22100%
get_DenseKeys()100%11100%
get_Keys()100%11100%
get_DenseValues()100%11100%
get_Values()100%11100%
get_Item(...)100%11100%
set_Item(...)100%22100%
get_State()100%11100%
set_State(...)100%1414100%
ContainsKey(...)100%22100%
TryAdd(...)100%22100%
Add(...)100%11100%
TryGetValue(...)100%44100%
Remove(...)100%66100%
Clear()100%44100%
EnsureDenseCapacity(...)100%88100%
EnsureSparseCapacity(...)100%88100%
TrimExcess()100%1414100%
GetEnumerator()100%11100%
System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<System.Int32,T>>.GetEnumerator()100%11100%
System.Collections.IEnumerable.GetEnumerator()100%11100%
.ctor(...)100%11100%
System.Collections.IEnumerator.get_Current()100%11100%
MoveNext()100%22100%
Reset()100%11100%
Dispose()100%11100%
GetDense(...)100%11100%
CopyKeysTo(...)100%11100%
CopySortedKeysTo(...)100%11100%
GetRequiredSparseCapacity(...)100%11100%
GetDenseIndexOrThrow(...)100%11100%
CloneTo(...)100%22100%

File(s)

/home/runner/work/SwiftCollections/SwiftCollections/src/SwiftCollections/Collection/SwiftSparseMap.cs

#LineLine coverage
 1//=======================================================================
 2// SwiftSparseMap.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2024–present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using System;
 9using System.Collections;
 10using System.Collections.Generic;
 11using System.Runtime.CompilerServices;
 12using System.Text.Json.Serialization;
 13using Chronicler;
 14using MemoryPack;
 15using SwiftCollections.Diagnostics;
 16using SwiftCollections.Utility;
 17
 18namespace SwiftCollections;
 19
 20/// <summary>
 21/// Represents a high-performance sparse map that stores values indexed by externally supplied integer keys.
 22/// Provides O(1) Add, Remove, Contains, and lookup operations while maintaining densely packed storage
 23/// for cache-friendly iteration.
 24/// </summary>
 25/// <remarks>
 26/// Unlike <see cref="SwiftBucket{T}"/>, which internally assigns and manages item indices,
 27/// <see cref="SwiftSparseMap{T}"/> is externally keyed. The caller supplies the integer key
 28/// (for example, an entity ID or handle) used to index the value.
 29///
 30/// Internally, the container maintains:
 31/// <list type="bullet">
 32///     <item>
 33///         <description>A sparse lookup table mapping keys to dense indices.</description>
 34///     </item>
 35///     <item>
 36///         <description>A dense array of keys.</description>
 37///     </item>
 38///     <item>
 39///         <description>A dense array of values.</description>
 40///     </item>
 41/// </list>
 42///
 43/// Removal uses a swap-back strategy to keep dense storage contiguous. As a result,
 44/// iteration order is not guaranteed to remain stable.
 45///
 46/// Keys are used as direct indices into the sparse lookup table, so memory usage scales
 47/// with the highest stored key rather than the number of stored values. This container is
 48/// intended for compact, non-negative IDs such as entity handles or slot indices. It is
 49/// not a good fit for arbitrary hashes or widely spaced keys; for those workloads prefer
 50/// <c>SwiftDictionary&lt;TKey, TValue&gt;</c>.
 51/// </remarks>
 52/// <typeparam name="T">Value type stored by key.</typeparam>
 53[Serializable]
 54[JsonConverter(typeof(StateJsonConverterFactory))]
 55[MemoryPackable]
 56public sealed partial class SwiftSparseMap<T> : IStateBacked<SwiftSparseMapState<T>>, ISwiftCloneable<T>, IEnumerable<Ke
 57{
 58    #region Constants
 59
 60    /// <summary>
 61    /// Represents the default initial capacity for dense collections.
 62    /// </summary>
 63    public const int DefaultDenseCapacity = 8;
 64
 65    /// <summary>
 66    /// Represents the default initial capacity for sparse collections.
 67    /// </summary>
 68    public const int DefaultSparseCapacity = 8;
 69
 70    /// <summary>
 71    /// Represents the value used to indicate that a key is not present in the sparse array.
 72    /// </summary>
 73    /// <remarks>
 74    /// A value of 0 signifies that the key is absent.
 75    /// When a key is present, the stored value is the dense index plus one.
 76    /// </remarks>
 77    private const int NotPresent = 0;
 78
 79    #endregion
 80
 81    #region Fields
 82
 83    private int[] _sparse;       // key -> denseIndex+1
 84    private int[] _denseKeys;    // denseIndex -> key
 85    private T[] _denseValues;    // denseIndex -> value
 86    private int _count;
 87
 88    [NonSerialized]
 89    private uint _version;
 90
 91    [NonSerialized]
 92    private object? _syncRoot;
 93
 94    #endregion
 95
 96    #region Constructors
 97
 98    /// <summary>
 99    /// Initializes a new instance of the SwiftSparseMap class with default sparse and dense capacities.
 100    /// </summary>
 76101    public SwiftSparseMap() : this(DefaultSparseCapacity, DefaultDenseCapacity) { }
 102
 103    /// <summary>
 104    /// Initializes a new sparse map with the specified sparse and dense capacities.
 105    /// </summary>
 106    /// <param name="sparseCapacity">
 107    /// Initial sparse lookup capacity. This should track the highest expected key plus one,
 108    /// not the number of stored values.
 109    /// </param>
 110    /// <param name="denseCapacity">Initial dense storage capacity for values.</param>
 42111    public SwiftSparseMap(int sparseCapacity, int denseCapacity)
 112    {
 42113        SwiftThrowHelper.ThrowIfNegative(sparseCapacity, nameof(sparseCapacity));
 42114        SwiftThrowHelper.ThrowIfNegative(denseCapacity, nameof(denseCapacity));
 115
 42116        int sparseSize = sparseCapacity == 0 ? 0 : SwiftHashTools.NextPowerOfTwo(sparseCapacity);
 42117        _sparse = sparseCapacity == 0
 42118            ? Array.Empty<int>()
 42119            : new int[sparseSize];
 42120        int denseSize = denseCapacity < DefaultDenseCapacity
 42121            ? DefaultDenseCapacity
 42122            : SwiftHashTools.NextPowerOfTwo(denseCapacity);
 42123        _denseKeys = denseCapacity == 0
 42124            ? Array.Empty<int>()
 42125            : new int[denseSize];
 42126        _denseValues = _denseKeys.Length == 0 ? Array.Empty<T>() : new T[_denseKeys.Length];
 127
 42128        _count = 0;
 42129    }
 130
 131    /// <summary>
 132    /// Initializes a new instance of the SwiftSparseMap class using the specified state.
 133    /// </summary>
 134    /// <param name="state">The state object that provides the initial configuration and data for the map. Cannot be nul
 135    [MemoryPackConstructor]
 7136    public SwiftSparseMap(SwiftSparseMapState<T> state)
 137    {
 7138        _sparse = Array.Empty<int>();
 7139        _denseKeys = Array.Empty<int>();
 7140        _denseValues = Array.Empty<T>();
 141
 7142        State = state;
 5143    }
 144
 145    #endregion
 146
 147    #region Properties
 148
 149    /// <summary>
 150    /// Gets the number of elements contained in the collection.
 151    /// </summary>
 152    [JsonIgnore]
 153    [MemoryPackIgnore]
 15154    public int Count => _count;
 155
 156    /// <summary>
 157    /// Capacity of the dense arrays (Keys/Values storage).
 158    /// </summary>
 159    [JsonIgnore]
 160    [MemoryPackIgnore]
 7161    public int DenseCapacity => _denseKeys.Length;
 162
 163    /// <summary>
 164    /// Capacity of the sparse array (max key+1 that can be mapped without resizing).
 165    /// Memory usage grows with this capacity.
 166    /// </summary>
 167    [JsonIgnore]
 168    [MemoryPackIgnore]
 8169    public int SparseCapacity => _sparse.Length;
 170
 171    /// <summary>
 172    /// Gets a value indicating whether access to the collection is synchronized (thread safe).
 173    /// </summary>
 174    [JsonIgnore]
 175    [MemoryPackIgnore]
 1176    public bool IsSynchronized => false;
 177
 178    /// <summary>
 179    /// Gets an object that can be used to synchronize access to the collection.
 180    /// </summary>
 181    /// <remarks>
 182    /// Use this object to lock the collection during multithreaded operations to ensure thread safety.
 183    /// The returned object is unique to this collection instance.
 184    /// </remarks>
 185    [JsonIgnore]
 186    [MemoryPackIgnore]
 3187    public object SyncRoot => _syncRoot ??= new object();
 188
 189    /// <summary>
 190    /// Returns the dense keys array (valid range: [0..Count)).
 191    /// </summary>
 192    /// <remarks>
 193    /// Prefer collection APIs. Direct key mutation must preserve the dense/sparse lookup invariants; invalid edits may 
 194    /// </remarks>
 195    [JsonIgnore]
 196    [MemoryPackIgnore]
 6197    public int[] DenseKeys => _denseKeys;
 198
 199    /// <summary>
 200    /// Gets a span containing the keys currently stored in the collection.
 201    /// </summary>
 202    /// <remarks>
 203    /// The returned span provides a view of the underlying key data and reflects the current state of the collection.
 204    /// Prefer collection APIs. Direct key mutation must preserve the dense/sparse lookup invariants; invalid edits may 
 205    /// The span is only valid as long as the underlying collection is not modified.
 206    /// </remarks>
 207    [JsonIgnore]
 208    [MemoryPackIgnore]
 1209    public Span<int> Keys => _denseKeys.AsSpan(0, _count);
 210
 211    /// <summary>
 212    /// Returns the dense values array (valid range: [0..Count)).
 213    /// </summary>
 214    [JsonIgnore]
 215    [MemoryPackIgnore]
 5216    public T[] DenseValues => _denseValues;
 217
 218    /// <summary>
 219    /// Gets a span containing the current values in the collection.
 220    /// </summary>
 221    /// <remarks>
 222    /// The returned span reflects the live contents of the collection up to the current count.
 223    /// Modifying the span will update the underlying collection data.
 224    /// The span length is equal to the number of elements currently stored.
 225    /// </remarks>
 226    [JsonIgnore]
 227    [MemoryPackIgnore]
 1228    public Span<T> Values => _denseValues.AsSpan(0, _count);
 229
 230    /// <summary>
 231    /// Gets/sets the value for a key. Setting:
 232    /// - overwrites if present
 233    /// - inserts if not present
 234    /// </summary>
 235    [JsonIgnore]
 236    [MemoryPackIgnore]
 237    public T this[int key]
 238    {
 239        get
 240        {
 24241            int denseIndex = GetDenseIndexOrThrow(key);
 21242            return _denseValues[denseIndex];
 243        }
 244        set
 245        {
 56246            EnsureSparseCapacity(GetRequiredSparseCapacity(key));
 247
 54248            int slot = _sparse[key];
 54249            if (slot != NotPresent)
 250            {
 251                // present -> overwrite
 1252                int denseIndex = slot - 1;
 1253                _denseValues[denseIndex] = value;
 1254                _version++;
 1255                return;
 256            }
 257
 258            // not present -> insert
 53259            EnsureDenseCapacity(_count + 1);
 260
 53261            int newIndex = _count++;
 53262            _denseKeys[newIndex] = key;
 53263            _denseValues[newIndex] = value;
 53264            _sparse[key] = newIndex + 1;
 265
 53266            _version++;
 53267        }
 268    }
 269
 270    /// <summary>
 271    /// Gets or sets the current state of the sparse map, including the used dense keys and values.
 272    /// </summary>
 273    /// <remarks>
 274    /// The state includes only the active elements in the map.
 275    /// Setting this property replaces the current contents with the provided state.
 276    /// The setter is intended for internal use, such as serialization or deserialization scenarios.
 277    /// </remarks>
 278    [JsonInclude]
 279    [MemoryPackInclude]
 280    public SwiftSparseMapState<T> State
 281    {
 282        get
 283        {
 284            // Serialize only the used portions of dense arrays
 4285            var denseKeys = new int[_count];
 4286            Array.Copy(_denseKeys, denseKeys, _count);
 287
 4288            var denseValues = new T[_count];
 4289            Array.Copy(_denseValues, denseValues, _count);
 290
 4291            return new SwiftSparseMapState<T>(denseKeys, denseValues);
 292        }
 293        internal set
 294        {
 7295            SwiftThrowHelper.ThrowIfNull(value.DenseKeys);
 7296            SwiftThrowHelper.ThrowIfNull(value.DenseValues);
 297
 7298            int n = value.DenseKeys.Length;
 299
 7300            SwiftThrowHelper.ThrowIfArgument(n != value.DenseValues.Length, nameof(value), "DenseKeys and DenseValues le
 301
 302            // Allocate dense storage
 6303            _denseKeys = n == 0 ? Array.Empty<int>() : new int[Math.Max(DefaultDenseCapacity, n)];
 6304            _denseValues = n == 0 ? Array.Empty<T>() : new T[_denseKeys.Length];
 305
 6306            if (n > 0)
 307            {
 5308                Array.Copy(value.DenseKeys, _denseKeys, n);
 5309                Array.Copy(value.DenseValues, _denseValues, n);
 310            }
 311
 6312            _count = n;
 313
 314            // Compute maxKey from dense keys
 6315            int maxKey = -1;
 30316            for (int i = 0; i < n; i++)
 317            {
 9318                int key = _denseKeys[i];
 9319                SwiftThrowHelper.ThrowIfArgument(key < 0, nameof(value), "Key cannot be negative.");
 320
 9321                if (key > maxKey)
 8322                    maxKey = key;
 323            }
 324
 325            // Allocate sparse map
 6326            int sparseSize = maxKey < 0
 6327                ? DefaultSparseCapacity
 6328                : Math.Max(DefaultSparseCapacity, GetRequiredSparseCapacity(maxKey));
 6329            _sparse = new int[sparseSize];
 330
 331            // Rebuild sparse lookup
 28332            for (int i = 0; i < n; i++)
 333            {
 9334                int key = _denseKeys[i];
 335
 9336                SwiftThrowHelper.ThrowIfArgument(_sparse[key] != NotPresent, nameof(value), "Duplicate key in DenseKeys.
 337
 8338                _sparse[key] = i + 1;
 339            }
 340
 5341            _version++;
 5342        }
 343    }
 344
 345    #endregion
 346
 347    #region Core Operations
 348
 349    /// <summary>
 350    /// Determines whether the collection contains the specified key.
 351    /// </summary>
 352    /// <param name="key">The key to locate in the collection.</param>
 353    /// <returns>true if the collection contains an element with the specified key; otherwise, false.</returns>
 354    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 355    public bool ContainsKey(int key)
 356    {
 12357        if ((uint)key >= (uint)_sparse.Length) return false;
 10358        return _sparse[key] != NotPresent;
 359    }
 360
 361    /// <summary>
 362    /// Adds a key/value only if the key is not present.
 363    /// Returns false if already present.
 364    /// </summary>
 365    public bool TryAdd(int key, T value)
 366    {
 2367        EnsureSparseCapacity(GetRequiredSparseCapacity(key));
 2368        if (_sparse[key] != NotPresent)
 1369            return false;
 370
 1371        EnsureDenseCapacity(_count + 1);
 372
 1373        int newIndex = _count++;
 1374        _denseKeys[newIndex] = key;
 1375        _denseValues[newIndex] = value;
 1376        _sparse[key] = newIndex + 1;
 377
 1378        _version++;
 1379        return true;
 380    }
 381
 382    /// <summary>
 383    /// Adds or overwrites (same behavior as indexer set).
 384    /// </summary>
 55385    public void Add(int key, T value) => this[key] = value;
 386
 387    /// <summary>
 388    /// Attempts to retrieve the value associated with the specified key.
 389    /// </summary>
 390    /// <param name="key">The key whose associated value is to be retrieved.</param>
 391    /// <param name="value">
 392    /// When this method returns, contains the value associated with the specified key, if the key is found;
 393    /// otherwise, the default value for the type parameter <typeparamref name="T"/>.
 394    /// This parameter is passed uninitialized.
 395    /// </param>
 396    /// <returns>true if the key was found and its value was retrieved; otherwise, false.</returns>
 397    public bool TryGetValue(int key, out T value)
 398    {
 3399        if ((uint)key < (uint)_sparse.Length)
 400        {
 2401            int slot = _sparse[key];
 2402            if (slot != NotPresent)
 403            {
 1404                value = _denseValues[slot - 1];
 1405                return true;
 406            }
 407        }
 408
 2409        value = default!;
 2410        return false;
 411    }
 412
 413    /// <summary>
 414    /// Removes the element with the specified key from the collection, if it exists.
 415    /// </summary>
 416    /// <param name="key">The key of the element to remove. Must be a non-negative integer within the valid range of key
 417    /// <returns>true if the element is successfully found and removed; otherwise, false.</returns>
 418    public bool Remove(int key)
 419    {
 8420        if ((uint)key >= (uint)_sparse.Length) return false;
 421
 6422        int slot = _sparse[key];
 7423        if (slot == NotPresent) return false;
 424
 5425        int index = slot - 1;
 5426        int last = --_count;
 427
 5428        _sparse[key] = NotPresent;
 429
 5430        if (index != last)
 431        {
 3432            int movedKey = _denseKeys[last];
 433
 3434            _denseKeys[index] = movedKey;
 3435            _denseValues[index] = _denseValues[last];
 436
 3437            _sparse[movedKey] = index + 1;
 438        }
 439
 5440        _denseKeys[last] = default;
 5441        _denseValues[last] = default!;
 442
 5443        _version++;
 5444        return true;
 445    }
 446
 447    /// <summary>
 448    /// Removes all keys and values from the collection.
 449    /// </summary>
 450    /// <remarks>
 451    /// After calling this method, the collection will be empty and its Count property will be zero.
 452    /// This method does not reduce the capacity of the underlying storage.
 453    /// </remarks>
 454    public void Clear()
 455    {
 7456        if (_count == 0) return;
 457
 458        // reset sparse for keys that were present
 14459        for (int i = 0; i < _count; i++)
 460        {
 4461            int key = _denseKeys[i];
 4462            _sparse[key] = NotPresent;
 463        }
 464
 3465        Array.Clear(_denseKeys, 0, _count);
 3466        Array.Clear(_denseValues, 0, _count);
 467
 3468        _count = 0;
 3469        _version++;
 3470    }
 471
 472    #endregion
 473
 474    #region Capacity Management
 475
 476    /// <summary>
 477    /// Ensures that the internal dense storage has at least the specified capacity, expanding it if necessary.
 478    /// </summary>
 479    /// <remarks>
 480    /// If the current capacity is less than the specified value, the internal storage is resized to accommodate at leas
 481    /// Existing elements are preserved.
 482    /// The capacity is increased to the next power of two greater than or equal to the requested capacity for performan
 483    /// </remarks>
 484    /// <param name="capacity">The minimum number of elements that the dense storage must be able to hold. Must be non-n
 485    public void EnsureDenseCapacity(int capacity)
 486    {
 111487        if (capacity <= _denseKeys.Length) return;
 488
 3489        int newCap = _denseKeys.Length == 0 ? DefaultDenseCapacity : _denseKeys.Length * 2;
 5490        if (newCap < capacity) newCap = capacity;
 491
 3492        newCap = SwiftHashTools.NextPowerOfTwo(newCap);
 493
 3494        var newKeys = new int[newCap];
 3495        var newVals = new T[newCap];
 496
 3497        if (_count > 0)
 498        {
 1499            Array.Copy(_denseKeys, newKeys, _count);
 1500            Array.Copy(_denseValues, newVals, _count);
 501        }
 502
 3503        _denseKeys = newKeys;
 3504        _denseValues = newVals;
 505
 3506        _version++;
 3507    }
 508
 509    /// <summary>
 510    /// Ensures that the internal sparse array has a capacity at least as large as the specified value.
 511    /// </summary>
 512    /// <remarks>
 513    /// If the current capacity is less than the specified value, the internal storage is resized to accommodate at leas
 514    /// Existing elements are preserved.
 515    /// The capacity is increased to the next power of two greater than or equal to the requested capacity for performan
 516    /// </remarks>
 517    /// <param name="capacity">The minimum required capacity for the internal sparse array. Must be non-negative.</param
 518    public void EnsureSparseCapacity(int capacity)
 519    {
 108520        if (capacity <= _sparse.Length) return;
 521
 8522        int newCap = _sparse.Length == 0
 8523            ? DefaultSparseCapacity
 8524            : _sparse.Length * 2;
 11525        if (newCap < capacity) newCap = capacity;
 526
 8527        newCap = SwiftHashTools.NextPowerOfTwo(newCap);
 528
 8529        var newSparse = new int[newCap];
 8530        if (_sparse.Length > 0)
 7531            Array.Copy(_sparse, newSparse, _sparse.Length);
 532
 8533        _sparse = newSparse;
 8534        _version++;
 8535    }
 536
 537    /// <summary>
 538    /// Reduces the memory usage of the collection by resizing internal storage to fit the current number of elements as
 539    /// </summary>
 540    /// <remarks>
 541    /// Call this method to minimize the collection's memory footprint after removing a significant number of elements.
 542    /// This operation may improve memory efficiency but can be an expensive operation if the collection is large.
 543    /// The method does not affect the logical contents of the collection.
 544    /// </remarks>
 545    public void TrimExcess()
 546    {
 547        // Dense: shrink to Count (with a minimum)
 4548        int newDense = Math.Max(DefaultDenseCapacity, _count);
 4549        if (newDense < _denseKeys.Length)
 550        {
 3551            var newKeys = new int[newDense];
 3552            var newVals = new T[newDense];
 3553            if (_count > 0)
 554            {
 2555                Array.Copy(_denseKeys, newKeys, _count);
 2556                Array.Copy(_denseValues, newVals, _count);
 557            }
 3558            _denseKeys = newKeys;
 3559            _denseValues = newVals;
 560        }
 561
 562        // Sparse: shrink to (maxKey+1) based on dense keys
 4563        int maxKey = -1;
 18564        for (int i = 0; i < _count; i++)
 8565            if (_denseKeys[i] > maxKey) maxKey = _denseKeys[i];
 566
 4567        int newSparse = maxKey < 0
 4568            ? DefaultSparseCapacity
 4569            : Math.Max(DefaultSparseCapacity, GetRequiredSparseCapacity(maxKey));
 4570        if (newSparse < _sparse.Length)
 571        {
 3572            var newMap = new int[newSparse];
 573            // rebuild from dense
 12574            for (int i = 0; i < _count; i++)
 3575                newMap[_denseKeys[i]] = i + 1;
 3576            _sparse = newMap;
 577        }
 578
 4579        _version++;
 4580    }
 581
 582    #endregion
 583
 584    #region Enumeration
 585
 586    /// <inheritdoc cref="IEnumerable.GetEnumerator()"/>
 10587    public SwiftSparseMapEnumerator GetEnumerator() => new(this);
 7588    IEnumerator<KeyValuePair<int, T>> IEnumerable<KeyValuePair<int, T>>.GetEnumerator() => GetEnumerator();
 1589    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
 590
 591    /// <summary>
 592    /// Supports iteration over the key/value pairs in a <see cref="SwiftSparseMap{T}"/> collection.
 593    /// </summary>
 594    /// <remarks>
 595    /// The enumerator provides a forward-only, read-only traversal of the collection.
 596    /// It is invalidated if the underlying collection is modified during enumeration, and any such modification will ca
 597    /// subsequent operations to throw an InvalidOperationException.
 598    /// </remarks>
 599    public struct SwiftSparseMapEnumerator : IEnumerator<KeyValuePair<int, T>>
 600    {
 601        private readonly SwiftSparseMap<T> _map;
 602        private readonly int[] _keys;
 603        private readonly T[] _values;
 604        private readonly int _count;
 605        private readonly uint _version;
 606        private int _index;
 607
 608        internal SwiftSparseMapEnumerator(SwiftSparseMap<T> map)
 609        {
 10610            _map = map;
 10611            _keys = map._denseKeys;
 10612            _values = map._denseValues;
 10613            _count = map._count;
 10614            _version = map._version;
 10615            _index = -1;
 10616            Current = default;
 10617        }
 618
 619        /// <inheritdoc/>
 620        public KeyValuePair<int, T> Current { get; private set; }
 1621        object IEnumerator.Current => Current;
 622
 623        /// <inheritdoc/>
 624        public bool MoveNext()
 625        {
 14626            SwiftThrowHelper.ThrowIfTrue(_version != _map._version, message: "Collection was modified during enumeration
 627
 13628            int next = _index + 1;
 13629            if (next >= _count)
 630            {
 7631                Current = default;
 7632                return false;
 633            }
 634
 6635            _index = next;
 6636            Current = new KeyValuePair<int, T>(_keys[_index], _values[_index]);
 6637            return true;
 638        }
 639
 640        /// <inheritdoc/>
 641        public void Reset()
 642        {
 1643            SwiftThrowHelper.ThrowIfTrue(_version != _map._version, message: "Collection was modified during enumeration
 644
 1645            _index = -1;
 1646            Current = default;
 1647        }
 648
 649        /// <inheritdoc/>
 7650        public void Dispose() => _index = -1;
 651    }
 652
 653    #endregion
 654
 655    #region Helpers
 656
 657    /// <summary>
 658    /// Retrieves the dense representation of the collection as parallel arrays of keys and values, along with the numbe
 659    /// </summary>
 660    /// <remarks>
 661    /// The arrays returned may be larger than the actual number of elements.
 662    /// Only the first <paramref name="count"/> entries in each array are valid and should be used.
 663    /// </remarks>
 664    /// <param name="keys">
 665    /// When this method returns, contains an array of keys representing the dense mapping.
 666    /// The array length is at least as large as the number of elements returned in <paramref name="count"/>.
 667    /// </param>
 668    /// <param name="values">
 669    /// When this method returns, contains an array of values corresponding to the keys in <paramref name="keys"/>.
 670    /// The array length is at least as large as the number of elements returned in <paramref name="count"/>.
 671    /// </param>
 672    /// <param name="count">When this method returns, contains the number of valid key-value pairs in the dense arrays.<
 673    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 674    public void GetDense(out int[] keys, out T[] values, out int count)
 675    {
 1676        keys = _denseKeys;
 1677        values = _denseValues;
 1678        count = _count;
 1679    }
 680
 681    /// <summary>
 682    /// Replaces the destination list contents with this map's keys in dense iteration order.
 683    /// </summary>
 684    /// <remarks>
 685    /// The destination list is reused and only grows when its current capacity is smaller than
 686    /// <see cref="Count"/>. Use <see cref="CopySortedKeysTo(SwiftList{int})"/> when stable ascending
 687    /// key order is required.
 688    /// </remarks>
 689    /// <param name="destination">The caller-owned list that receives the keys.</param>
 690    public void CopyKeysTo(SwiftList<int> destination)
 691    {
 4692        SwiftThrowHelper.ThrowIfNull(destination, nameof(destination));
 693
 4694        destination.FastClear();
 4695        destination.AddRange(_denseKeys.AsSpan(0, _count));
 4696    }
 697
 698    /// <summary>
 699    /// Replaces the destination list contents with this map's keys sorted in ascending order.
 700    /// </summary>
 701    /// <remarks>
 702    /// This method is intended for reusable hot-path scratch buffers that need deterministic key order
 703    /// without constructing a persistent sorted collection.
 704    /// </remarks>
 705    /// <param name="destination">The caller-owned list that receives the sorted keys.</param>
 706    public void CopySortedKeysTo(SwiftList<int> destination)
 707    {
 3708        CopyKeysTo(destination);
 3709        destination.SortInPlace(default(SwiftIntAscendingComparer));
 3710    }
 711
 712    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 713    private static int GetRequiredSparseCapacity(int key)
 714    {
 66715        SwiftThrowHelper.ThrowIfNegative(key, nameof(key));
 65716        SwiftThrowHelper.ThrowIfArgumentOutOfRange(key == int.MaxValue, key, nameof(key), "Key is too large for direct s
 717
 64718        return key + 1;
 719    }
 720
 721    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 722    private int GetDenseIndexOrThrow(int key)
 723    {
 24724        SwiftThrowHelper.ThrowIfKeyNotFound((uint)key >= (uint)_sparse.Length, key);
 725
 22726        int slot = _sparse[key];
 727
 22728        SwiftThrowHelper.ThrowIfKeyNotFound(slot == NotPresent, key);
 729
 21730        return slot - 1;
 731    }
 732
 733    /// <inheritdoc/>
 734    public void CloneTo(ICollection<T> output)
 735    {
 1736        SwiftThrowHelper.ThrowIfNull(output, nameof(output));
 737
 1738        output.Clear();
 739
 6740        for (int i = 0; i < _count; i++)
 2741            output.Add(_denseValues[i]);
 1742    }
 743
 744    #endregion
 745}