< 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: 219
Uncovered lines: 0
Coverable lines: 219
Total lines: 743
Line coverage: 100%
Branch coverage
93%
Covered branches: 77
Total branches: 82
Branch coverage: 93.9%
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()50%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()83.33%66100%
EnsureDenseCapacity(...)100%88100%
EnsureSparseCapacity(...)100%88100%
TrimExcess()78.57%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 Chronicler;
 9using MemoryPack;
 10using SwiftCollections.Diagnostics;
 11using SwiftCollections.Utility;
 12using System;
 13using System.Collections;
 14using System.Collections.Generic;
 15using System.Runtime.CompilerServices;
 16using System.Text.Json.Serialization;
 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>
 78101    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]
 16154    public int Count => _count;
 155
 156    /// <summary>
 157    /// Capacity of the dense arrays (Keys/Values storage).
 158    /// </summary>
 159    [JsonIgnore]
 160    [MemoryPackIgnore]
 5161    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]
 6169    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]
 1187    public object SyncRoot => _syncRoot ??= new object();
 188
 189    /// <summary>
 190    /// Returns the dense keys array (valid range: [0..Count)).
 191    /// </summary>
 192    [JsonIgnore]
 193    [MemoryPackIgnore]
 6194    public int[] DenseKeys => _denseKeys;
 195
 196    /// <summary>
 197    /// Gets a span containing the keys currently stored in the collection.
 198    /// </summary>
 199    /// <remarks>
 200    /// The returned span provides a view of the underlying key data and reflects the current state of the collection.
 201    /// Modifying the span will affect the collection's contents.
 202    /// The span is only valid as long as the underlying collection is not modified.
 203    /// </remarks>
 204    [JsonIgnore]
 205    [MemoryPackIgnore]
 1206    public Span<int> Keys => _denseKeys.AsSpan(0, _count);
 207
 208    /// <summary>
 209    /// Returns the dense values array (valid range: [0..Count)).
 210    /// </summary>
 211    [JsonIgnore]
 212    [MemoryPackIgnore]
 5213    public T[] DenseValues => _denseValues;
 214
 215    /// <summary>
 216    /// Gets a span containing the current values in the collection.
 217    /// </summary>
 218    /// <remarks>
 219    /// The returned span reflects the live contents of the collection up to the current count.
 220    /// Modifying the span will update the underlying collection data.
 221    /// The span length is equal to the number of elements currently stored.
 222    /// </remarks>
 223    [JsonIgnore]
 224    [MemoryPackIgnore]
 1225    public Span<T> Values => _denseValues.AsSpan(0, _count);
 226
 227    /// <summary>
 228    /// Gets/sets the value for a key. Setting:
 229    /// - overwrites if present
 230    /// - inserts if not present
 231    /// </summary>
 232    [JsonIgnore]
 233    [MemoryPackIgnore]
 234    public T this[int key]
 235    {
 236        get
 237        {
 22238            int denseIndex = GetDenseIndexOrThrow(key);
 19239            return _denseValues[denseIndex];
 240        }
 241        set
 242        {
 58243            EnsureSparseCapacity(GetRequiredSparseCapacity(key));
 244
 56245            int slot = _sparse[key];
 56246            if (slot != NotPresent)
 247            {
 248                // present -> overwrite
 1249                int denseIndex = slot - 1;
 1250                _denseValues[denseIndex] = value;
 1251                _version++;
 1252                return;
 253            }
 254
 255            // not present -> insert
 55256            EnsureDenseCapacity(_count + 1);
 257
 55258            int newIndex = _count++;
 55259            _denseKeys[newIndex] = key;
 55260            _denseValues[newIndex] = value;
 55261            _sparse[key] = newIndex + 1;
 262
 55263            _version++;
 55264        }
 265    }
 266
 267    /// <summary>
 268    /// Gets or sets the current state of the sparse map, including the used dense keys and values.
 269    /// </summary>
 270    /// <remarks>
 271    /// The state includes only the active elements in the map.
 272    /// Setting this property replaces the current contents with the provided state.
 273    /// The setter is intended for internal use, such as serialization or deserialization scenarios.
 274    /// </remarks>
 275    [JsonInclude]
 276    [MemoryPackInclude]
 277    public SwiftSparseMapState<T> State
 278    {
 279        get
 280        {
 281            // Serialize only the used portions of dense arrays
 4282            var denseKeys = new int[_count];
 4283            Array.Copy(_denseKeys, denseKeys, _count);
 284
 4285            var denseValues = new T[_count];
 4286            Array.Copy(_denseValues, denseValues, _count);
 287
 4288            return new SwiftSparseMapState<T>(denseKeys, denseValues);
 289        }
 290        internal set
 291        {
 7292            SwiftThrowHelper.ThrowIfNull(value.DenseKeys);
 7293            SwiftThrowHelper.ThrowIfNull(value.DenseValues);
 294
 7295            int n = value.DenseKeys.Length;
 296
 7297            SwiftThrowHelper.ThrowIfArgument(n != value.DenseValues.Length, nameof(value), "DenseKeys and DenseValues le
 298
 299            // Allocate dense storage
 6300            _denseKeys = n == 0 ? Array.Empty<int>() : new int[Math.Max(DefaultDenseCapacity, n)];
 6301            _denseValues = n == 0 ? Array.Empty<T>() : new T[_denseKeys.Length];
 302
 6303            if (n > 0)
 304            {
 5305                Array.Copy(value.DenseKeys, _denseKeys, n);
 5306                Array.Copy(value.DenseValues, _denseValues, n);
 307            }
 308
 6309            _count = n;
 310
 311            // Compute maxKey from dense keys
 6312            int maxKey = -1;
 30313            for (int i = 0; i < n; i++)
 314            {
 9315                int key = _denseKeys[i];
 9316                SwiftThrowHelper.ThrowIfArgument(key < 0, nameof(value), "Key cannot be negative.");
 317
 9318                if (key > maxKey)
 8319                    maxKey = key;
 320            }
 321
 322            // Allocate sparse map
 6323            int sparseSize = maxKey < 0
 6324                ? DefaultSparseCapacity
 6325                : Math.Max(DefaultSparseCapacity, GetRequiredSparseCapacity(maxKey));
 6326            _sparse = new int[sparseSize];
 327
 328            // Rebuild sparse lookup
 28329            for (int i = 0; i < n; i++)
 330            {
 9331                int key = _denseKeys[i];
 332
 9333                SwiftThrowHelper.ThrowIfArgument(_sparse[key] != NotPresent, nameof(value), "Duplicate key in DenseKeys.
 334
 8335                _sparse[key] = i + 1;
 336            }
 337
 5338            _version++;
 5339        }
 340    }
 341
 342    #endregion
 343
 344    #region Core Operations
 345
 346    /// <summary>
 347    /// Determines whether the collection contains the specified key.
 348    /// </summary>
 349    /// <param name="key">The key to locate in the collection.</param>
 350    /// <returns>true if the collection contains an element with the specified key; otherwise, false.</returns>
 351    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 352    public bool ContainsKey(int key)
 353    {
 12354        if ((uint)key >= (uint)_sparse.Length) return false;
 10355        return _sparse[key] != NotPresent;
 356    }
 357
 358    /// <summary>
 359    /// Adds a key/value only if the key is not present.
 360    /// Returns false if already present.
 361    /// </summary>
 362    public bool TryAdd(int key, T value)
 363    {
 2364        EnsureSparseCapacity(GetRequiredSparseCapacity(key));
 2365        if (_sparse[key] != NotPresent)
 1366            return false;
 367
 1368        EnsureDenseCapacity(_count + 1);
 369
 1370        int newIndex = _count++;
 1371        _denseKeys[newIndex] = key;
 1372        _denseValues[newIndex] = value;
 1373        _sparse[key] = newIndex + 1;
 374
 1375        _version++;
 1376        return true;
 377    }
 378
 379    /// <summary>
 380    /// Adds or overwrites (same behavior as indexer set).
 381    /// </summary>
 57382    public void Add(int key, T value) => this[key] = value;
 383
 384    /// <summary>
 385    /// Attempts to retrieve the value associated with the specified key.
 386    /// </summary>
 387    /// <param name="key">The key whose associated value is to be retrieved.</param>
 388    /// <param name="value">
 389    /// When this method returns, contains the value associated with the specified key, if the key is found;
 390    /// otherwise, the default value for the type parameter <typeparamref name="T"/>.
 391    /// This parameter is passed uninitialized.
 392    /// </param>
 393    /// <returns>true if the key was found and its value was retrieved; otherwise, false.</returns>
 394    public bool TryGetValue(int key, out T value)
 395    {
 3396        if ((uint)key < (uint)_sparse.Length)
 397        {
 2398            int slot = _sparse[key];
 2399            if (slot != NotPresent)
 400            {
 1401                value = _denseValues[slot - 1];
 1402                return true;
 403            }
 404        }
 405
 2406        value = default!;
 2407        return false;
 408    }
 409
 410    /// <summary>
 411    /// Removes the element with the specified key from the collection, if it exists.
 412    /// </summary>
 413    /// <param name="key">The key of the element to remove. Must be a non-negative integer within the valid range of key
 414    /// <returns>true if the element is successfully found and removed; otherwise, false.</returns>
 415    public bool Remove(int key)
 416    {
 8417        if ((uint)key >= (uint)_sparse.Length) return false;
 418
 6419        int slot = _sparse[key];
 7420        if (slot == NotPresent) return false;
 421
 5422        int index = slot - 1;
 5423        int last = --_count;
 424
 5425        _sparse[key] = NotPresent;
 426
 5427        if (index != last)
 428        {
 3429            int movedKey = _denseKeys[last];
 430
 3431            _denseKeys[index] = movedKey;
 3432            _denseValues[index] = _denseValues[last];
 433
 3434            _sparse[movedKey] = index + 1;
 435        }
 436
 5437        _denseKeys[last] = default;
 5438        _denseValues[last] = default!;
 439
 5440        _version++;
 5441        return true;
 442    }
 443
 444    /// <summary>
 445    /// Removes all keys and values from the collection.
 446    /// </summary>
 447    /// <remarks>
 448    /// After calling this method, the collection will be empty and its Count property will be zero.
 449    /// This method does not reduce the capacity of the underlying storage.
 450    /// </remarks>
 451    public void Clear()
 452    {
 7453        if (_count == 0) return;
 454
 455        // reset sparse for keys that were present
 14456        for (int i = 0; i < _count; i++)
 457        {
 4458            int key = _denseKeys[i];
 4459            if ((uint)key < (uint)_sparse.Length)
 4460                _sparse[key] = NotPresent;
 461        }
 462
 3463        Array.Clear(_denseKeys, 0, _count);
 3464        Array.Clear(_denseValues, 0, _count);
 465
 3466        _count = 0;
 3467        _version++;
 3468    }
 469
 470    #endregion
 471
 472    #region Capacity Management
 473
 474    /// <summary>
 475    /// Ensures that the internal dense storage has at least the specified capacity, expanding it if necessary.
 476    /// </summary>
 477    /// <remarks>
 478    /// If the current capacity is less than the specified value, the internal storage is resized to accommodate at leas
 479    /// Existing elements are preserved.
 480    /// The capacity is increased to the next power of two greater than or equal to the requested capacity for performan
 481    /// </remarks>
 482    /// <param name="capacity">The minimum number of elements that the dense storage must be able to hold. Must be non-n
 483    public void EnsureDenseCapacity(int capacity)
 484    {
 115485        if (capacity <= _denseKeys.Length) return;
 486
 3487        int newCap = _denseKeys.Length == 0 ? DefaultDenseCapacity : _denseKeys.Length * 2;
 5488        if (newCap < capacity) newCap = capacity;
 489
 3490        newCap = SwiftHashTools.NextPowerOfTwo(newCap);
 491
 3492        var newKeys = new int[newCap];
 3493        var newVals = new T[newCap];
 494
 3495        if (_count > 0)
 496        {
 1497            Array.Copy(_denseKeys, newKeys, _count);
 1498            Array.Copy(_denseValues, newVals, _count);
 499        }
 500
 3501        _denseKeys = newKeys;
 3502        _denseValues = newVals;
 503
 3504        _version++;
 3505    }
 506
 507    /// <summary>
 508    /// Ensures that the internal sparse array has a capacity at least as large as the specified value.
 509    /// </summary>
 510    /// <remarks>
 511    /// If the current capacity is less than the specified value, the internal storage is resized to accommodate at leas
 512    /// Existing elements are preserved.
 513    /// The capacity is increased to the next power of two greater than or equal to the requested capacity for performan
 514    /// </remarks>
 515    /// <param name="capacity">The minimum required capacity for the internal sparse array. Must be non-negative.</param
 516    public void EnsureSparseCapacity(int capacity)
 517    {
 111518        if (capacity <= _sparse.Length) return;
 519
 9520        int newCap = _sparse.Length == 0
 9521            ? DefaultSparseCapacity
 9522            : _sparse.Length * 2;
 12523        if (newCap < capacity) newCap = capacity;
 524
 9525        newCap = SwiftHashTools.NextPowerOfTwo(newCap);
 526
 9527        var newSparse = new int[newCap];
 9528        if (_sparse.Length > 0)
 8529            Array.Copy(_sparse, newSparse, _sparse.Length);
 530
 9531        _sparse = newSparse;
 9532        _version++;
 9533    }
 534
 535    /// <summary>
 536    /// Reduces the memory usage of the collection by resizing internal storage to fit the current number of elements as
 537    /// </summary>
 538    /// <remarks>
 539    /// Call this method to minimize the collection's memory footprint after removing a significant number of elements.
 540    /// This operation may improve memory efficiency but can be an expensive operation if the collection is large.
 541    /// The method does not affect the logical contents of the collection.
 542    /// </remarks>
 543    public void TrimExcess()
 544    {
 545        // Dense: shrink to Count (with a minimum)
 2546        int newDense = Math.Max(DefaultDenseCapacity, _count);
 2547        if (newDense < _denseKeys.Length)
 548        {
 2549            var newKeys = new int[newDense];
 2550            var newVals = new T[newDense];
 2551            if (_count > 0)
 552            {
 1553                Array.Copy(_denseKeys, newKeys, _count);
 1554                Array.Copy(_denseValues, newVals, _count);
 555            }
 2556            _denseKeys = newKeys;
 2557            _denseValues = newVals;
 558        }
 559
 560        // Sparse: shrink to (maxKey+1) based on dense keys
 2561        int maxKey = -1;
 6562        for (int i = 0; i < _count; i++)
 2563            if (_denseKeys[i] > maxKey) maxKey = _denseKeys[i];
 564
 2565        int newSparse = maxKey < 0
 2566            ? DefaultSparseCapacity
 2567            : Math.Max(DefaultSparseCapacity, GetRequiredSparseCapacity(maxKey));
 2568        if (newSparse < _sparse.Length)
 569        {
 2570            var newMap = new int[newSparse];
 571            // rebuild from dense
 6572            for (int i = 0; i < _count; i++)
 1573                newMap[_denseKeys[i]] = i + 1;
 2574            _sparse = newMap;
 575        }
 576
 2577        _version++;
 2578    }
 579
 580    #endregion
 581
 582    #region Enumeration
 583
 584    /// <inheritdoc cref="IEnumerable.GetEnumerator()"/>
 10585    public SwiftSparseMapEnumerator GetEnumerator() => new(this);
 7586    IEnumerator<KeyValuePair<int, T>> IEnumerable<KeyValuePair<int, T>>.GetEnumerator() => GetEnumerator();
 1587    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
 588
 589    /// <summary>
 590    /// Supports iteration over the key/value pairs in a <see cref="SwiftSparseMap{T}"/> collection.
 591    /// </summary>
 592    /// <remarks>
 593    /// The enumerator provides a forward-only, read-only traversal of the collection.
 594    /// It is invalidated if the underlying collection is modified during enumeration, and any such modification will ca
 595    /// subsequent operations to throw an InvalidOperationException.
 596    /// </remarks>
 597    public struct SwiftSparseMapEnumerator : IEnumerator<KeyValuePair<int, T>>
 598    {
 599        private readonly SwiftSparseMap<T> _map;
 600        private readonly int[] _keys;
 601        private readonly T[] _values;
 602        private readonly int _count;
 603        private readonly uint _version;
 604        private int _index;
 605
 606        internal SwiftSparseMapEnumerator(SwiftSparseMap<T> map)
 607        {
 10608            _map = map;
 10609            _keys = map._denseKeys;
 10610            _values = map._denseValues;
 10611            _count = map._count;
 10612            _version = map._version;
 10613            _index = -1;
 10614            Current = default;
 10615        }
 616
 617        /// <inheritdoc/>
 618        public KeyValuePair<int, T> Current { get; private set; }
 1619        object IEnumerator.Current => Current;
 620
 621        /// <inheritdoc/>
 622        public bool MoveNext()
 623        {
 14624            SwiftThrowHelper.ThrowIfTrue(_version != _map._version, message: "Collection was modified during enumeration
 625
 13626            int next = _index + 1;
 13627            if (next >= _count)
 628            {
 7629                Current = default;
 7630                return false;
 631            }
 632
 6633            _index = next;
 6634            Current = new KeyValuePair<int, T>(_keys[_index], _values[_index]);
 6635            return true;
 636        }
 637
 638        /// <inheritdoc/>
 639        public void Reset()
 640        {
 1641            SwiftThrowHelper.ThrowIfTrue(_version != _map._version, message: "Collection was modified during enumeration
 642
 1643            _index = -1;
 1644            Current = default;
 1645        }
 646
 647        /// <inheritdoc/>
 7648        public void Dispose() => _index = -1;
 649    }
 650
 651    #endregion
 652
 653    #region Helpers
 654
 655    /// <summary>
 656    /// Retrieves the dense representation of the collection as parallel arrays of keys and values, along with the numbe
 657    /// </summary>
 658    /// <remarks>
 659    /// The arrays returned may be larger than the actual number of elements.
 660    /// Only the first <paramref name="count"/> entries in each array are valid and should be used.
 661    /// </remarks>
 662    /// <param name="keys">
 663    /// When this method returns, contains an array of keys representing the dense mapping.
 664    /// The array length is at least as large as the number of elements returned in <paramref name="count"/>.
 665    /// </param>
 666    /// <param name="values">
 667    /// When this method returns, contains an array of values corresponding to the keys in <paramref name="keys"/>.
 668    /// The array length is at least as large as the number of elements returned in <paramref name="count"/>.
 669    /// </param>
 670    /// <param name="count">When this method returns, contains the number of valid key-value pairs in the dense arrays.<
 671    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 672    public void GetDense(out int[] keys, out T[] values, out int count)
 673    {
 1674        keys = _denseKeys;
 1675        values = _denseValues;
 1676        count = _count;
 1677    }
 678
 679    /// <summary>
 680    /// Replaces the destination list contents with this map's keys in dense iteration order.
 681    /// </summary>
 682    /// <remarks>
 683    /// The destination list is reused and only grows when its current capacity is smaller than
 684    /// <see cref="Count"/>. Use <see cref="CopySortedKeysTo(SwiftList{int})"/> when stable ascending
 685    /// key order is required.
 686    /// </remarks>
 687    /// <param name="destination">The caller-owned list that receives the keys.</param>
 688    public void CopyKeysTo(SwiftList<int> destination)
 689    {
 6690        SwiftThrowHelper.ThrowIfNull(destination, nameof(destination));
 691
 6692        destination.FastClear();
 6693        destination.AddRange(_denseKeys.AsSpan(0, _count));
 6694    }
 695
 696    /// <summary>
 697    /// Replaces the destination list contents with this map's keys sorted in ascending order.
 698    /// </summary>
 699    /// <remarks>
 700    /// This method is intended for reusable hot-path scratch buffers that need deterministic key order
 701    /// without constructing a persistent sorted collection.
 702    /// </remarks>
 703    /// <param name="destination">The caller-owned list that receives the sorted keys.</param>
 704    public void CopySortedKeysTo(SwiftList<int> destination)
 705    {
 4706        CopyKeysTo(destination);
 4707        destination.SortInPlace(default(SwiftIntAscendingComparer));
 4708    }
 709
 710    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 711    private static int GetRequiredSparseCapacity(int key)
 712    {
 66713        SwiftThrowHelper.ThrowIfNegative(key, nameof(key));
 65714        SwiftThrowHelper.ThrowIfArgumentOutOfRange(key == int.MaxValue, key, nameof(key), "Key is too large for direct s
 715
 64716        return key + 1;
 717    }
 718
 719    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 720    private int GetDenseIndexOrThrow(int key)
 721    {
 22722        SwiftThrowHelper.ThrowIfKeyNotFound((uint)key >= (uint)_sparse.Length, key);
 723
 20724        int slot = _sparse[key];
 725
 20726        SwiftThrowHelper.ThrowIfKeyNotFound(slot == NotPresent, key);
 727
 19728        return slot - 1;
 729    }
 730
 731    /// <inheritdoc/>
 732    public void CloneTo(ICollection<T> output)
 733    {
 1734        SwiftThrowHelper.ThrowIfNull(output, nameof(output));
 735
 1736        output.Clear();
 737
 6738        for (int i = 0; i < _count; i++)
 2739            output.Add(_denseValues[i]);
 1740    }
 741
 742    #endregion
 743}