< Summary

Information
Class: SwiftCollections.SwiftSparseSet
Assembly: SwiftCollections
File(s): /home/runner/work/SwiftCollections/SwiftCollections/src/SwiftCollections/Collection/SwiftSparseSet.cs
Line coverage
100%
Covered lines: 310
Uncovered lines: 0
Coverable lines: 310
Total lines: 845
Line coverage: 100%
Branch coverage
100%
Covered branches: 172
Total branches: 172
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%11100%
.ctor(...)100%88100%
.ctor(...)100%11100%
get_Count()100%11100%
get_DenseCapacity()100%11100%
get_SparseCapacity()100%11100%
get_IsReadOnly()100%11100%
get_IsSynchronized()100%11100%
get_SyncRoot()100%22100%
get_DenseKeys()100%11100%
get_Keys()100%11100%
get_State()100%11100%
set_State(...)100%11100%
RestoreDenseKeys(...)100%44100%
ValidateDenseKeys(...)100%44100%
RestoreSparseLookup(...)100%44100%
Contains(...)100%22100%
ContainsKey(...)100%11100%
Add(...)100%22100%
TryAdd(...)100%11100%
System.Collections.Generic.ICollection<System.Int32>.Add(...)100%11100%
Remove(...)100%66100%
Clear()100%44100%
ExceptWith(...)100%66100%
IntersectWith(...)100%88100%
IsProperSubsetOf(...)100%1010100%
IsProperSupersetOf(...)100%1010100%
IsSubsetOf(...)100%88100%
IsSupersetOf(...)100%66100%
Overlaps(...)100%44100%
SetEquals(...)100%66100%
SymmetricExceptWith(...)100%66100%
UnionWith(...)100%22100%
RemoveWhereMissingFrom(...)100%44100%
RemoveWhereMissingFrom(...)100%44100%
AllKeysIn(...)100%44100%
AllKeysIn(...)100%44100%
IsSubsetOfSparseSet(...)100%22100%
IsSubsetOfSet(...)100%22100%
SetEqualsSparseSet(...)100%22100%
SetEqualsSet(...)100%22100%
SymmetricExceptWithSet(...)100%44100%
EnsureDenseCapacity(...)100%88100%
EnsureSparseCapacity(...)100%88100%
TrimExcess()100%11100%
TrimDenseStorage()100%44100%
TrimSparseLookup()100%1010100%
AsReadOnlySpan()100%11100%
GetDense(...)100%11100%
CopyKeysTo(...)100%11100%
CopySortedKeysTo(...)100%11100%
CopyTo(...)100%11100%
System.Collections.ICollection.CopyTo(...)100%88100%
CloneTo(...)100%22100%
GetEnumerator()100%11100%
System.Collections.Generic.IEnumerable<System.Int32>.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%
GetRequiredSparseCapacity(...)100%11100%

File(s)

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

#LineLine coverage
 1//=======================================================================
 2// SwiftSparseSet.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 set for externally supplied non-negative integer IDs.
 22/// Provides O(1) Add, Remove, Contains, and densely packed iteration.
 23/// </summary>
 24/// <remarks>
 25/// <para>
 26/// <see cref="SwiftSparseSet"/> is intended for membership workloads where the caller already owns
 27/// compact integer IDs, such as entity handles, body IDs, or slot indices.
 28/// </para>
 29/// <para>
 30/// Internally, IDs are stored in a dense array for cache-friendly iteration while a sparse lookup
 31/// table maps each ID directly to its dense position. Removal uses swap-back, so iteration order is
 32/// not stable.
 33/// </para>
 34/// <para>
 35/// Memory usage scales with the highest stored ID rather than only the number of IDs. For arbitrary,
 36/// huge, or widely spaced keys, prefer <see cref="SwiftHashSet{T}"/> with <c>int</c> keys.
 37/// </para>
 38/// </remarks>
 39[Serializable]
 40[JsonConverter(typeof(StateJsonConverterFactory))]
 41[MemoryPackable]
 42public sealed partial class SwiftSparseSet : IStateBacked<SwiftArrayState<int>>, ISwiftCloneable<int>, ISet<int>, IReadO
 43{
 44    #region Constants
 45
 46    /// <summary>
 47    /// Represents the default initial capacity for dense ID storage.
 48    /// </summary>
 49    public const int DefaultDenseCapacity = 8;
 50
 51    /// <summary>
 52    /// Represents the default initial capacity for sparse ID lookup.
 53    /// </summary>
 54    public const int DefaultSparseCapacity = 8;
 55
 56    private const int NotPresent = 0;
 57
 58    #endregion
 59
 60    #region Fields
 61
 62    private int[] _sparse;       // id -> denseIndex+1
 63    private int[] _denseKeys;    // denseIndex -> id
 64    private int _count;
 65
 66    [NonSerialized]
 67    private uint _version;
 68
 69    [NonSerialized]
 70    private object? _syncRoot;
 71
 72    #endregion
 73
 74    #region Constructors
 75
 76    /// <summary>
 77    /// Initializes a new instance of the <see cref="SwiftSparseSet"/> class with default sparse and dense capacities.
 78    /// </summary>
 8079    public SwiftSparseSet() : this(DefaultSparseCapacity, DefaultDenseCapacity) { }
 80
 81    /// <summary>
 82    /// Initializes a new instance of the <see cref="SwiftSparseSet"/> class with matching sparse and dense capacities.
 83    /// </summary>
 84    /// <param name="capacity">The initial sparse and dense capacity.</param>
 285    public SwiftSparseSet(int capacity) : this(capacity, capacity) { }
 86
 87    /// <summary>
 88    /// Initializes a new instance of the <see cref="SwiftSparseSet"/> class with explicit sparse and dense capacities.
 89    /// </summary>
 90    /// <param name="sparseCapacity">
 91    /// Initial sparse lookup capacity. This should track the highest expected ID plus one,
 92    /// not just the number of stored IDs.
 93    /// </param>
 94    /// <param name="denseCapacity">Initial dense storage capacity for IDs.</param>
 4595    public SwiftSparseSet(int sparseCapacity, int denseCapacity)
 96    {
 4597        SwiftThrowHelper.ThrowIfNegative(sparseCapacity, nameof(sparseCapacity));
 4598        SwiftThrowHelper.ThrowIfNegative(denseCapacity, nameof(denseCapacity));
 99
 45100        int sparseSize = sparseCapacity == 0 ? 0 : SwiftHashTools.NextPowerOfTwo(sparseCapacity);
 45101        _sparse = sparseCapacity == 0
 45102            ? Array.Empty<int>()
 45103            : new int[sparseSize];
 104
 45105        int denseSize = denseCapacity < DefaultDenseCapacity
 45106            ? DefaultDenseCapacity
 45107            : SwiftHashTools.NextPowerOfTwo(denseCapacity);
 45108        _denseKeys = denseCapacity == 0
 45109            ? Array.Empty<int>()
 45110            : new int[denseSize];
 45111    }
 112
 113    /// <summary>
 114    /// Initializes a new instance of the <see cref="SwiftSparseSet"/> class using the specified state.
 115    /// </summary>
 116    /// <param name="state">The state object that provides the initial IDs. Cannot be null.</param>
 117    [MemoryPackConstructor]
 5118    public SwiftSparseSet(SwiftArrayState<int> state)
 119    {
 5120        _sparse = Array.Empty<int>();
 5121        _denseKeys = Array.Empty<int>();
 122
 5123        State = state;
 3124    }
 125
 126    #endregion
 127
 128    #region Properties
 129
 130    /// <summary>
 131    /// Gets the number of IDs contained in the set.
 132    /// </summary>
 133    [JsonIgnore]
 134    [MemoryPackIgnore]
 4135    public int Count => _count;
 136
 137    /// <summary>
 138    /// Capacity of the dense ID storage.
 139    /// </summary>
 140    [JsonIgnore]
 141    [MemoryPackIgnore]
 9142    public int DenseCapacity => _denseKeys.Length;
 143
 144    /// <summary>
 145    /// Capacity of the sparse lookup table.
 146    /// </summary>
 147    [JsonIgnore]
 148    [MemoryPackIgnore]
 9149    public int SparseCapacity => _sparse.Length;
 150
 151    /// <inheritdoc/>
 152    [JsonIgnore]
 153    [MemoryPackIgnore]
 1154    public bool IsReadOnly => false;
 155
 156    /// <summary>
 157    /// Gets a value indicating whether access to the collection is synchronized.
 158    /// </summary>
 159    [JsonIgnore]
 160    [MemoryPackIgnore]
 1161    public bool IsSynchronized => false;
 162
 163    /// <summary>
 164    /// Gets an object that can be used to synchronize access to the collection.
 165    /// </summary>
 166    [JsonIgnore]
 167    [MemoryPackIgnore]
 3168    public object SyncRoot => _syncRoot ??= new object();
 169
 170    /// <summary>
 171    /// Returns the dense ID array. Only the range [0..Count) is populated.
 172    /// </summary>
 173    /// <remarks>
 174    /// Prefer collection APIs. Direct key mutation must preserve the dense/sparse lookup invariants; invalid edits may 
 175    /// </remarks>
 176    [JsonIgnore]
 177    [MemoryPackIgnore]
 4178    public int[] DenseKeys => _denseKeys;
 179
 180    /// <summary>
 181    /// Gets a span containing the current IDs in dense iteration order.
 182    /// </summary>
 183    /// <remarks>
 184    /// Prefer collection APIs. Direct key mutation must preserve the dense/sparse lookup invariants; invalid edits may 
 185    /// </remarks>
 186    [JsonIgnore]
 187    [MemoryPackIgnore]
 3188    public Span<int> Keys => _denseKeys.AsSpan(0, _count);
 189
 190    /// <summary>
 191    /// Gets or sets the current state of the sparse set.
 192    /// </summary>
 193    [JsonInclude]
 194    [MemoryPackInclude]
 195    public SwiftArrayState<int> State
 196    {
 197        get
 198        {
 2199            var items = new int[_count];
 2200            Array.Copy(_denseKeys, items, _count);
 2201            return new SwiftArrayState<int>(items);
 202        }
 203        internal set
 204        {
 5205            SwiftThrowHelper.ThrowIfNull(value.Items, nameof(value.Items));
 206
 5207            RestoreDenseKeys(value.Items);
 5208            int maxKey = ValidateDenseKeys(nameof(value.Items));
 4209            RestoreSparseLookup(maxKey, nameof(value));
 210
 3211            _version++;
 3212        }
 213    }
 214
 215    private void RestoreDenseKeys(int[] items)
 216    {
 5217        int count = items.Length;
 5218        _denseKeys = count == 0
 5219            ? Array.Empty<int>()
 5220            : new int[Math.Max(DefaultDenseCapacity, SwiftHashTools.NextPowerOfTwo(count))];
 221
 5222        if (count > 0)
 4223            Array.Copy(items, _denseKeys, count);
 224
 5225        _count = count;
 5226    }
 227
 228    private int ValidateDenseKeys(string paramName)
 229    {
 5230        int maxKey = -1;
 20231        for (int i = 0; i < _count; i++)
 232        {
 6233            int key = _denseKeys[i];
 6234            SwiftThrowHelper.ThrowIfNegative(key, paramName);
 5235            SwiftThrowHelper.ThrowIfArgumentOutOfRange(key == int.MaxValue, key, paramName, "ID is too large for direct 
 236
 5237            if (key > maxKey)
 4238                maxKey = key;
 239        }
 240
 4241        return maxKey;
 242    }
 243
 244    private void RestoreSparseLookup(int maxKey, string paramName)
 245    {
 4246        int sparseSize = maxKey < 0
 4247            ? DefaultSparseCapacity
 4248            : Math.Max(DefaultSparseCapacity, GetRequiredSparseCapacity(maxKey));
 4249        _sparse = new int[sparseSize];
 250
 16251        for (int i = 0; i < _count; i++)
 252        {
 5253            int key = _denseKeys[i];
 5254            SwiftThrowHelper.ThrowIfArgument(_sparse[key] != NotPresent, paramName, "Duplicate ID in sparse set state.")
 4255            _sparse[key] = i + 1;
 256        }
 3257    }
 258
 259    #endregion
 260
 261    #region Core Operations
 262
 263    /// <summary>
 264    /// Determines whether the set contains the specified ID.
 265    /// </summary>
 266    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 267    public bool Contains(int item)
 268    {
 52269        if ((uint)item >= (uint)_sparse.Length) return false;
 42270        return _sparse[item] != NotPresent;
 271    }
 272
 273    /// <summary>
 274    /// Determines whether the set contains the specified key. Alias for <see cref="Contains(int)"/>.
 275    /// </summary>
 276    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 5277    public bool ContainsKey(int key) => Contains(key);
 278
 279    /// <summary>
 280    /// Adds the specified ID if it is not already present.
 281    /// </summary>
 282    /// <returns>true if the ID was added; false if it was already present.</returns>
 283    public bool Add(int item)
 284    {
 92285        EnsureSparseCapacity(GetRequiredSparseCapacity(item));
 90286        if (_sparse[item] != NotPresent)
 1287            return false;
 288
 89289        EnsureDenseCapacity(_count + 1);
 290
 89291        int newIndex = _count++;
 89292        _denseKeys[newIndex] = item;
 89293        _sparse[item] = newIndex + 1;
 294
 89295        _version++;
 89296        return true;
 297    }
 298
 299    /// <summary>
 300    /// Adds the specified ID if it is not already present.
 301    /// </summary>
 1302    public bool TryAdd(int item) => Add(item);
 303
 1304    void ICollection<int>.Add(int item) => Add(item);
 305
 306    /// <summary>
 307    /// Removes the specified ID from the set.
 308    /// </summary>
 309    /// <returns>true if the ID was found and removed; otherwise, false.</returns>
 310    public bool Remove(int item)
 311    {
 24312        if ((uint)item >= (uint)_sparse.Length) return false;
 313
 18314        int slot = _sparse[item];
 22315        if (slot == NotPresent) return false;
 316
 14317        int index = slot - 1;
 14318        int last = --_count;
 319
 14320        _sparse[item] = NotPresent;
 321
 14322        if (index != last)
 323        {
 12324            int movedKey = _denseKeys[last];
 12325            _denseKeys[index] = movedKey;
 12326            _sparse[movedKey] = index + 1;
 327        }
 328
 14329        _denseKeys[last] = default;
 14330        _version++;
 14331        return true;
 332    }
 333
 334    /// <summary>
 335    /// Removes all IDs from the set without reducing capacity.
 336    /// </summary>
 337    public void Clear()
 338    {
 5339        if (_count == 0) return;
 340
 18341        for (int i = 0; i < _count; i++)
 342        {
 6343            int key = _denseKeys[i];
 6344            _sparse[key] = NotPresent;
 6345            _denseKeys[i] = default;
 346        }
 347
 3348        _count = 0;
 3349        _version++;
 3350    }
 351
 352    #endregion
 353
 354    #region Set Operations
 355
 356    /// <inheritdoc/>
 357    public void ExceptWith(IEnumerable<int> other)
 358    {
 3359        SwiftThrowHelper.ThrowIfNull(other, nameof(other));
 360
 4361        if (_count == 0) return;
 2362        if (ReferenceEquals(other, this))
 363        {
 1364            Clear();
 1365            return;
 366        }
 367
 6368        foreach (int item in other)
 2369            Remove(item);
 1370    }
 371
 372    /// <inheritdoc/>
 373    public void IntersectWith(IEnumerable<int> other)
 374    {
 5375        SwiftThrowHelper.ThrowIfNull(other, nameof(other));
 376
 7377        if (_count == 0 || ReferenceEquals(other, this)) return;
 378
 3379        if (other is SwiftSparseSet sparseSet)
 380        {
 1381            RemoveWhereMissingFrom(sparseSet);
 1382            return;
 383        }
 384
 2385        if (other is ISet<int> set)
 386        {
 1387            RemoveWhereMissingFrom(set);
 1388            return;
 389        }
 390
 1391        RemoveWhereMissingFrom(new HashSet<int>(other));
 1392    }
 393
 394    /// <inheritdoc/>
 395    public bool IsProperSubsetOf(IEnumerable<int> other)
 396    {
 6397        SwiftThrowHelper.ThrowIfNull(other, nameof(other));
 398
 6399        if (other is SwiftSparseSet sparseSet)
 2400            return _count < sparseSet._count && IsSubsetOf(sparseSet);
 401
 4402        if (other is ISet<int> set)
 2403            return _count < set.Count && IsSubsetOf(set);
 404
 2405        var lookup = new HashSet<int>(other);
 2406        return _count < lookup.Count && IsSubsetOf(lookup);
 407    }
 408
 409    /// <inheritdoc/>
 410    public bool IsProperSupersetOf(IEnumerable<int> other)
 411    {
 6412        SwiftThrowHelper.ThrowIfNull(other, nameof(other));
 413
 6414        if (other is SwiftSparseSet sparseSet)
 2415            return _count > sparseSet._count && IsSupersetOf(sparseSet);
 416
 4417        if (other is ISet<int> set)
 2418            return _count > set.Count && IsSupersetOf(set);
 419
 2420        var lookup = new HashSet<int>(other);
 2421        return _count > lookup.Count && IsSupersetOf(lookup);
 422    }
 423
 424    /// <inheritdoc/>
 425    public bool IsSubsetOf(IEnumerable<int> other)
 426    {
 10427        SwiftThrowHelper.ThrowIfNull(other, nameof(other));
 428
 12429        if (_count == 0 || ReferenceEquals(other, this)) return true;
 430
 8431        if (other is SwiftSparseSet sparseSet)
 3432            return IsSubsetOfSparseSet(sparseSet);
 433
 5434        if (other is ISet<int> set)
 4435            return IsSubsetOfSet(set);
 436
 1437        return IsSubsetOfSet(new HashSet<int>(other));
 438    }
 439
 440    /// <inheritdoc/>
 441    public bool IsSupersetOf(IEnumerable<int> other)
 442    {
 6443        SwiftThrowHelper.ThrowIfNull(other, nameof(other));
 444
 7445        if (ReferenceEquals(other, this)) return true;
 446
 29447        foreach (int item in other)
 448        {
 10449            if (!Contains(item))
 1450                return false;
 451        }
 452
 4453        return true;
 1454    }
 455
 456    /// <inheritdoc/>
 457    public bool Overlaps(IEnumerable<int> other)
 458    {
 2459        SwiftThrowHelper.ThrowIfNull(other, nameof(other));
 460
 9461        foreach (int item in other)
 462        {
 3463            if (Contains(item))
 1464                return true;
 465        }
 466
 1467        return false;
 1468    }
 469
 470    /// <inheritdoc/>
 471    public bool SetEquals(IEnumerable<int> other)
 472    {
 16473        SwiftThrowHelper.ThrowIfNull(other, nameof(other));
 474
 17475        if (ReferenceEquals(other, this)) return true;
 476
 15477        if (other is SwiftSparseSet sparseSet)
 4478            return SetEqualsSparseSet(sparseSet);
 479
 11480        if (other is ISet<int> set)
 4481            return SetEqualsSet(set);
 482
 7483        return SetEqualsSet(new HashSet<int>(other));
 484    }
 485
 486    /// <inheritdoc/>
 487    public void SymmetricExceptWith(IEnumerable<int> other)
 488    {
 5489        SwiftThrowHelper.ThrowIfNull(other, nameof(other));
 490
 5491        if (ReferenceEquals(other, this))
 492        {
 1493            Clear();
 1494            return;
 495        }
 496
 4497        if (other is SwiftSparseSet sparseSet)
 498        {
 1499            SymmetricExceptWithSet(sparseSet);
 1500            return;
 501        }
 502
 3503        if (other is ISet<int> set)
 504        {
 1505            SymmetricExceptWithSet(set);
 1506            return;
 507        }
 508
 2509        SymmetricExceptWithSet(new HashSet<int>(other));
 2510    }
 511
 512    /// <inheritdoc/>
 513    public void UnionWith(IEnumerable<int> other)
 514    {
 1515        SwiftThrowHelper.ThrowIfNull(other, nameof(other));
 516
 6517        foreach (int item in other)
 2518            Add(item);
 1519    }
 520
 521    private void RemoveWhereMissingFrom(SwiftSparseSet other)
 522    {
 1523        int index = 0;
 4524        while (index < _count)
 525        {
 3526            int key = _denseKeys[index];
 3527            if (other.Contains(key))
 2528                index++;
 529            else
 1530                Remove(key);
 531        }
 1532    }
 533
 534    private void RemoveWhereMissingFrom(ISet<int> other)
 535    {
 2536        int index = 0;
 9537        while (index < _count)
 538        {
 7539            int key = _denseKeys[index];
 7540            if (other.Contains(key))
 4541                index++;
 542            else
 3543                Remove(key);
 544        }
 2545    }
 546
 547    private bool AllKeysIn(SwiftSparseSet other)
 548    {
 28549        for (int i = 0; i < _count; i++)
 550        {
 10551            if (!other.Contains(_denseKeys[i]))
 1552                return false;
 553        }
 554
 4555        return true;
 556    }
 557
 558    private bool AllKeysIn(ISet<int> other)
 559    {
 82560        for (int i = 0; i < _count; i++)
 561        {
 30562            if (!other.Contains(_denseKeys[i]))
 3563                return false;
 564        }
 565
 11566        return true;
 567    }
 568
 569    private bool IsSubsetOfSparseSet(SwiftSparseSet other) =>
 3570        _count <= other._count && AllKeysIn(other);
 571
 572    private bool IsSubsetOfSet(ISet<int> other) =>
 5573        _count <= other.Count && AllKeysIn(other);
 574
 575    private bool SetEqualsSparseSet(SwiftSparseSet other) =>
 4576        _count == other._count && AllKeysIn(other);
 577
 578    private bool SetEqualsSet(ISet<int> other) =>
 11579        _count == other.Count && AllKeysIn(other);
 580
 581    private void SymmetricExceptWithSet(IEnumerable<int> other)
 582    {
 26583        foreach (int item in other)
 584        {
 9585            if (!Remove(item))
 4586                Add(item);
 587        }
 4588    }
 589
 590    #endregion
 591
 592    #region Capacity Management
 593
 594    /// <summary>
 595    /// Ensures that dense storage can hold at least the specified number of IDs.
 596    /// </summary>
 597    public void EnsureDenseCapacity(int capacity)
 598    {
 179599        if (capacity <= _denseKeys.Length) return;
 600
 3601        int newCap = _denseKeys.Length == 0 ? DefaultDenseCapacity : _denseKeys.Length * 2;
 5602        if (newCap < capacity) newCap = capacity;
 603
 3604        newCap = SwiftHashTools.NextPowerOfTwo(newCap);
 605
 3606        var newKeys = new int[newCap];
 3607        if (_count > 0)
 1608            Array.Copy(_denseKeys, newKeys, _count);
 609
 3610        _denseKeys = newKeys;
 3611        _version++;
 3612    }
 613
 614    /// <summary>
 615    /// Ensures that the sparse lookup table has at least the specified capacity.
 616    /// </summary>
 617    public void EnsureSparseCapacity(int capacity)
 618    {
 174619        if (capacity <= _sparse.Length) return;
 620
 8621        int newCap = _sparse.Length == 0
 8622            ? DefaultSparseCapacity
 8623            : _sparse.Length * 2;
 10624        if (newCap < capacity) newCap = capacity;
 625
 8626        newCap = SwiftHashTools.NextPowerOfTwo(newCap);
 627
 8628        var newSparse = new int[newCap];
 8629        if (_sparse.Length > 0)
 7630            Array.Copy(_sparse, newSparse, _sparse.Length);
 631
 8632        _sparse = newSparse;
 8633        _version++;
 8634    }
 635
 636    /// <summary>
 637    /// Reduces unused dense and sparse capacity while preserving all IDs.
 638    /// </summary>
 639    public void TrimExcess()
 640    {
 5641        TrimDenseStorage();
 5642        TrimSparseLookup();
 5643        _version++;
 5644    }
 645
 646    private void TrimDenseStorage()
 647    {
 5648        int newDense = Math.Max(DefaultDenseCapacity, _count);
 7649        if (newDense >= _denseKeys.Length) return;
 650
 3651        var newKeys = new int[newDense];
 3652        if (_count > 0)
 2653            Array.Copy(_denseKeys, newKeys, _count);
 3654        _denseKeys = newKeys;
 3655    }
 656
 657    private void TrimSparseLookup()
 658    {
 5659        int maxKey = -1;
 20660        for (int i = 0; i < _count; i++)
 8661            if (_denseKeys[i] > maxKey) maxKey = _denseKeys[i];
 662
 5663        int newSparse = maxKey < 0
 5664            ? DefaultSparseCapacity
 5665            : Math.Max(DefaultSparseCapacity, GetRequiredSparseCapacity(maxKey));
 7666        if (newSparse >= _sparse.Length) return;
 667
 3668        var newMap = new int[newSparse];
 12669        for (int i = 0; i < _count; i++)
 3670            newMap[_denseKeys[i]] = i + 1;
 3671        _sparse = newMap;
 3672    }
 673
 674    #endregion
 675
 676    #region Copy and Enumeration
 677
 678    /// <summary>
 679    /// Returns a read-only span over the populated dense ID range.
 680    /// </summary>
 1681    public ReadOnlySpan<int> AsReadOnlySpan() => _denseKeys.AsSpan(0, _count);
 682
 683    /// <summary>
 684    /// Retrieves the backing dense ID array and current count.
 685    /// </summary>
 686    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 687    public void GetDense(out int[] keys, out int count)
 688    {
 1689        keys = _denseKeys;
 1690        count = _count;
 1691    }
 692
 693    /// <summary>
 694    /// Replaces the destination list contents with this set's keys in dense iteration order.
 695    /// </summary>
 696    /// <remarks>
 697    /// The destination list is reused and only grows when its current capacity is smaller than
 698    /// <see cref="Count"/>. Use <see cref="CopySortedKeysTo(SwiftList{int})"/> when stable ascending
 699    /// key order is required.
 700    /// </remarks>
 701    /// <param name="destination">The caller-owned list that receives the keys.</param>
 702    public void CopyKeysTo(SwiftList<int> destination)
 703    {
 4704        SwiftThrowHelper.ThrowIfNull(destination, nameof(destination));
 705
 4706        destination.FastClear();
 4707        destination.AddRange(_denseKeys.AsSpan(0, _count));
 4708    }
 709
 710    /// <summary>
 711    /// Replaces the destination list contents with this set's keys sorted in ascending order.
 712    /// </summary>
 713    /// <remarks>
 714    /// This method is intended for reusable hot-path scratch buffers that need deterministic key order
 715    /// without constructing a persistent sorted collection.
 716    /// </remarks>
 717    /// <param name="destination">The caller-owned list that receives the sorted keys.</param>
 718    public void CopySortedKeysTo(SwiftList<int> destination)
 719    {
 3720        CopyKeysTo(destination);
 3721        destination.SortInPlace(default(SwiftIntAscendingComparer));
 3722    }
 723
 724    /// <inheritdoc/>
 725    public void CopyTo(int[] array, int arrayIndex)
 726    {
 3727        SwiftThrowHelper.ThrowIfNull(array, nameof(array));
 3728        SwiftThrowHelper.ThrowIfArrayIndexInvalid(arrayIndex, array.Length);
 3729        SwiftThrowHelper.ThrowIfArgument(array.Length - arrayIndex < _count, nameof(array), "The array is not large enou
 730
 3731        Array.Copy(_denseKeys, 0, array, arrayIndex, _count);
 3732    }
 733
 734    /// <inheritdoc/>
 735    void ICollection.CopyTo(Array array, int index)
 736    {
 8737        SwiftThrowHelper.ThrowIfNull(array, nameof(array));
 8738        SwiftThrowHelper.ThrowIfArgument(array.Rank != 1, nameof(array), "Only single dimensional arrays are supported."
 7739        SwiftThrowHelper.ThrowIfArgument(array.GetLowerBound(0) != 0, nameof(array), "Non-zero lower bound arrays are no
 6740        SwiftThrowHelper.ThrowIfArrayIndexInvalid(index, array.Length, nameof(index));
 5741        SwiftThrowHelper.ThrowIfArgument(array.Length - index < _count, nameof(array), "The array is not large enough to
 742
 4743        if (array is int[] intArray)
 744        {
 1745            CopyTo(intArray, index);
 1746            return;
 747        }
 748
 3749        Type elementType = array.GetType().GetElementType()!;
 3750        if (array is object[] objects && elementType.IsAssignableFrom(typeof(int)))
 751        {
 6752            for (int i = 0; i < _count; i++)
 2753                objects[index + i] = _denseKeys[i];
 1754            return;
 755        }
 756
 2757        throw new ArgumentException("Invalid array type.", nameof(array));
 758    }
 759
 760    /// <inheritdoc/>
 761    public void CloneTo(ICollection<int> output)
 762    {
 1763        SwiftThrowHelper.ThrowIfNull(output, nameof(output));
 764
 1765        output.Clear();
 766
 6767        for (int i = 0; i < _count; i++)
 2768            output.Add(_denseKeys[i]);
 1769    }
 770
 771    /// <inheritdoc cref="IEnumerable.GetEnumerator()"/>
 18772    public SwiftSparseSetEnumerator GetEnumerator() => new(this);
 15773    IEnumerator<int> IEnumerable<int>.GetEnumerator() => GetEnumerator();
 1774    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
 775
 776    /// <summary>
 777    /// Supports iteration over IDs in a <see cref="SwiftSparseSet"/>.
 778    /// </summary>
 779    public struct SwiftSparseSetEnumerator : IEnumerator<int>
 780    {
 781        private readonly SwiftSparseSet _set;
 782        private readonly int[] _keys;
 783        private readonly int _count;
 784        private readonly uint _version;
 785        private int _index;
 786
 787        internal SwiftSparseSetEnumerator(SwiftSparseSet set)
 788        {
 18789            _set = set;
 18790            _keys = set._denseKeys;
 18791            _count = set._count;
 18792            _version = set._version;
 18793            _index = -1;
 18794            Current = default;
 18795        }
 796
 797        /// <inheritdoc/>
 798        public int Current { get; private set; }
 1799        object IEnumerator.Current => Current;
 800
 801        /// <inheritdoc/>
 802        public bool MoveNext()
 803        {
 28804            SwiftThrowHelper.ThrowIfTrue(_version != _set._version, message: "Collection was modified during enumeration
 805
 27806            int next = _index + 1;
 27807            if (next >= _count)
 808            {
 15809                Current = default;
 15810                return false;
 811            }
 812
 12813            _index = next;
 12814            Current = _keys[_index];
 12815            return true;
 816        }
 817
 818        /// <inheritdoc/>
 819        public void Reset()
 820        {
 1821            SwiftThrowHelper.ThrowIfTrue(_version != _set._version, message: "Collection was modified during enumeration
 822
 1823            _index = -1;
 1824            Current = default;
 1825        }
 826
 827        /// <inheritdoc/>
 15828        public void Dispose() => _index = -1;
 829    }
 830
 831    #endregion
 832
 833    #region Helpers
 834
 835    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 836    private static int GetRequiredSparseCapacity(int key)
 837    {
 98838        SwiftThrowHelper.ThrowIfNegative(key, nameof(key));
 97839        SwiftThrowHelper.ThrowIfArgumentOutOfRange(key == int.MaxValue, key, nameof(key), "ID is too large for direct sp
 840
 96841        return key + 1;
 842    }
 843
 844    #endregion
 845}

Methods/Properties

.ctor()
.ctor(System.Int32)
.ctor(System.Int32,System.Int32)
.ctor(SwiftCollections.SwiftArrayState`1<System.Int32>)
get_Count()
get_DenseCapacity()
get_SparseCapacity()
get_IsReadOnly()
get_IsSynchronized()
get_SyncRoot()
get_DenseKeys()
get_Keys()
get_State()
set_State(SwiftCollections.SwiftArrayState`1<System.Int32>)
RestoreDenseKeys(System.Int32[])
ValidateDenseKeys(System.String)
RestoreSparseLookup(System.Int32,System.String)
Contains(System.Int32)
ContainsKey(System.Int32)
Add(System.Int32)
TryAdd(System.Int32)
System.Collections.Generic.ICollection<System.Int32>.Add(System.Int32)
Remove(System.Int32)
Clear()
ExceptWith(System.Collections.Generic.IEnumerable`1<System.Int32>)
IntersectWith(System.Collections.Generic.IEnumerable`1<System.Int32>)
IsProperSubsetOf(System.Collections.Generic.IEnumerable`1<System.Int32>)
IsProperSupersetOf(System.Collections.Generic.IEnumerable`1<System.Int32>)
IsSubsetOf(System.Collections.Generic.IEnumerable`1<System.Int32>)
IsSupersetOf(System.Collections.Generic.IEnumerable`1<System.Int32>)
Overlaps(System.Collections.Generic.IEnumerable`1<System.Int32>)
SetEquals(System.Collections.Generic.IEnumerable`1<System.Int32>)
SymmetricExceptWith(System.Collections.Generic.IEnumerable`1<System.Int32>)
UnionWith(System.Collections.Generic.IEnumerable`1<System.Int32>)
RemoveWhereMissingFrom(SwiftCollections.SwiftSparseSet)
RemoveWhereMissingFrom(System.Collections.Generic.ISet`1<System.Int32>)
AllKeysIn(SwiftCollections.SwiftSparseSet)
AllKeysIn(System.Collections.Generic.ISet`1<System.Int32>)
IsSubsetOfSparseSet(SwiftCollections.SwiftSparseSet)
IsSubsetOfSet(System.Collections.Generic.ISet`1<System.Int32>)
SetEqualsSparseSet(SwiftCollections.SwiftSparseSet)
SetEqualsSet(System.Collections.Generic.ISet`1<System.Int32>)
SymmetricExceptWithSet(System.Collections.Generic.IEnumerable`1<System.Int32>)
EnsureDenseCapacity(System.Int32)
EnsureSparseCapacity(System.Int32)
TrimExcess()
TrimDenseStorage()
TrimSparseLookup()
AsReadOnlySpan()
GetDense(System.Int32[]&,System.Int32&)
CopyKeysTo(SwiftCollections.SwiftList`1<System.Int32>)
CopySortedKeysTo(SwiftCollections.SwiftList`1<System.Int32>)
CopyTo(System.Int32[],System.Int32)
System.Collections.ICollection.CopyTo(System.Array,System.Int32)
CloneTo(System.Collections.Generic.ICollection`1<System.Int32>)
GetEnumerator()
System.Collections.Generic.IEnumerable<System.Int32>.GetEnumerator()
System.Collections.IEnumerable.GetEnumerator()
.ctor(SwiftCollections.SwiftSparseSet)
System.Collections.IEnumerator.get_Current()
MoveNext()
Reset()
Dispose()
GetRequiredSparseCapacity(System.Int32)