| | 1 | | using System; |
| | 2 | | using System.Collections; |
| | 3 | | using System.Collections.Generic; |
| | 4 | |
|
| | 5 | | namespace Itinero.Network.DataStructures; |
| | 6 | |
|
| | 7 | | internal sealed class SparseArray<T> : IEnumerable<(long i, T value)> |
| | 8 | | { |
| | 9 | | private T[][] _blocks; |
| | 10 | | private readonly int _blockSize; // Holds the maximum array size, always needs to be a power of 2. |
| | 11 | | private readonly int _arrayPow; |
| | 12 | | private long _size; // the total size of this array. |
| | 13 | | private readonly T _default; |
| | 14 | |
|
| | 15 | | public SparseArray(long size, int blockSize = 1 << 16, |
| | 16 | | T emptyDefault = default) |
| | 17 | | { |
| | 18 | | if (size < 0) |
| | 19 | | { |
| | 20 | | throw new ArgumentOutOfRangeException(nameof(size), "Size needs to be bigger than or equal to zero."); |
| | 21 | | } |
| | 22 | |
|
| | 23 | | if (blockSize <= 0) |
| | 24 | | { |
| | 25 | | throw new ArgumentOutOfRangeException(nameof(blockSize), |
| | 26 | | "Block size needs to be bigger than or equal to zero."); |
| | 27 | | } |
| | 28 | |
|
| | 29 | | if ((blockSize & (blockSize - 1)) != 0) |
| | 30 | | { |
| | 31 | | throw new ArgumentOutOfRangeException(nameof(blockSize), "Block size needs to be a power of 2."); |
| | 32 | | } |
| | 33 | |
|
| | 34 | | _default = emptyDefault; |
| | 35 | | _blockSize = blockSize; |
| | 36 | | _size = size; |
| | 37 | | _arrayPow = ExpOf2(blockSize); |
| | 38 | |
|
| | 39 | | var blockCount = (long)Math.Ceiling((double)size / _blockSize); |
| | 40 | | _blocks = new T[blockCount][]; |
| | 41 | | } |
| | 42 | |
|
| | 43 | | private SparseArray(T[][] blocks, long size, int blockSize, int arrayPow, T @default) |
| | 44 | | { |
| | 45 | | _blocks = blocks; |
| | 46 | | _size = size; |
| | 47 | | _blockSize = blockSize; |
| | 48 | | _arrayPow = arrayPow; |
| | 49 | | _default = @default; |
| | 50 | | } |
| | 51 | |
|
| | 52 | | private static int ExpOf2(int powerOf2) |
| | 53 | | { |
| | 54 | | // this can probably be faster but it needs to run once in the constructor, |
| | 55 | | // feel free to improve but not crucial. |
| | 56 | | if (powerOf2 == 1) |
| | 57 | | { |
| | 58 | | return 0; |
| | 59 | | } |
| | 60 | |
|
| | 61 | | return ExpOf2(powerOf2 / 2) + 1; |
| | 62 | | } |
| | 63 | |
|
| | 64 | | /// <summary> |
| | 65 | | /// Gets or sets the item at the given index. |
| | 66 | | /// </summary> |
| | 67 | | /// <param name="idx">The index.</param> |
| | 68 | | public T this[long idx] |
| | 69 | | { |
| | 70 | | get |
| | 71 | | { |
| | 72 | | if (idx >= this.Length) |
| | 73 | | { |
| | 74 | | throw new ArgumentOutOfRangeException(nameof(idx)); |
| | 75 | | } |
| | 76 | |
|
| | 77 | | var blockId = idx >> _arrayPow; |
| | 78 | | var block = _blocks[blockId]; |
| | 79 | | if (block == null) |
| | 80 | | { |
| | 81 | | return _default; |
| | 82 | | } |
| | 83 | |
|
| | 84 | | var localIdx = idx - (blockId << _arrayPow); |
| | 85 | | return block[localIdx]; |
| | 86 | | } |
| | 87 | | set |
| | 88 | | { |
| | 89 | | if (idx >= this.Length) |
| | 90 | | { |
| | 91 | | throw new ArgumentOutOfRangeException(nameof(idx)); |
| | 92 | | } |
| | 93 | |
|
| | 94 | | var blockId = idx >> _arrayPow; |
| | 95 | | var block = _blocks[blockId]; |
| | 96 | | if (block == null) |
| | 97 | | { |
| | 98 | | // don't create a new block for a default value. |
| | 99 | | if (EqualityComparer<T>.Default.Equals(value, _default)) |
| | 100 | | { |
| | 101 | | return; |
| | 102 | | } |
| | 103 | |
|
| | 104 | | block = new T[_blockSize]; |
| | 105 | | for (var i = 0; i < _blockSize; i++) |
| | 106 | | { |
| | 107 | | block[i] = _default; |
| | 108 | | } |
| | 109 | |
|
| | 110 | | _blocks[blockId] = block; |
| | 111 | | } |
| | 112 | |
|
| | 113 | | var localIdx = idx % _blockSize; |
| | 114 | | _blocks[blockId][localIdx] = value; |
| | 115 | | } |
| | 116 | | } |
| | 117 | |
|
| | 118 | | /// <summary> |
| | 119 | | /// Resizes this array to the given size. |
| | 120 | | /// </summary> |
| | 121 | | /// <param name="size">The size.</param> |
| | 122 | | /// <exception cref="ArgumentOutOfRangeException"></exception> |
| | 123 | | public void Resize(long size) |
| | 124 | | { |
| | 125 | | if (size < 0) |
| | 126 | | { |
| | 127 | | throw new ArgumentOutOfRangeException(nameof(size), |
| | 128 | | "Cannot resize an array to a size of zero or smaller."); |
| | 129 | | } |
| | 130 | |
|
| | 131 | | _size = size; |
| | 132 | |
|
| | 133 | | var blockCount = (long)Math.Ceiling((double)size / _blockSize); |
| | 134 | | if (blockCount != _blocks.Length) |
| | 135 | | { |
| | 136 | | Array.Resize(ref _blocks, (int)blockCount); |
| | 137 | | } |
| | 138 | | } |
| | 139 | |
|
| | 140 | | /// <summary> |
| | 141 | | /// Gets the length of this array. |
| | 142 | | /// </summary> |
| | 143 | | public long Length => _size; |
| | 144 | |
|
| | 145 | | /// <summary> |
| | 146 | | /// Creates a clone. |
| | 147 | | /// </summary> |
| | 148 | | /// <returns>A copy of this array.</returns> |
| | 149 | | public SparseArray<T> Clone() |
| | 150 | | { |
| | 151 | | var blocks = new T[_blocks.Length][]; |
| | 152 | | for (var b = 0; b < blocks.Length; b++) |
| | 153 | | { |
| | 154 | | var block = _blocks[b]; |
| | 155 | | if (block == null) |
| | 156 | | { |
| | 157 | | continue; |
| | 158 | | } |
| | 159 | |
|
| | 160 | | blocks[b] = (block.Clone() as T[])!; |
| | 161 | | } |
| | 162 | |
|
| | 163 | | return new SparseArray<T>(blocks, _size, _blockSize, _arrayPow, _default); |
| | 164 | | } |
| | 165 | |
|
| | 166 | | public IEnumerator<(long i, T value)> GetEnumerator() |
| | 167 | | { |
| | 168 | | for (var b = 0; b < _blocks.Length; b++) |
| | 169 | | { |
| | 170 | | var block = _blocks[b]; |
| | 171 | | if (block == null) |
| | 172 | | { |
| | 173 | | continue; |
| | 174 | | } |
| | 175 | |
|
| | 176 | | for (var i = 0; i < block.Length; i++) |
| | 177 | | { |
| | 178 | | yield return ((_blockSize * b) + i, block[i]); |
| | 179 | | } |
| | 180 | | } |
| | 181 | | } |
| | 182 | |
|
| | 183 | | IEnumerator IEnumerable.GetEnumerator() |
| | 184 | | { |
| | 185 | | return this.GetEnumerator(); |
| | 186 | | } |
| | 187 | | } |
| | 188 | |
|
| | 189 | | internal static class SparseArrayExtensions |
| | 190 | | { |
| | 191 | | internal static void EnsureMinimumSize<T>(this SparseArray<T> array, long i) |
| 1431 | 192 | | { |
| 1431 | 193 | | if (array.Length <= i) |
| 258 | 194 | | { |
| 258 | 195 | | array.Resize(i + 1); |
| 258 | 196 | | } |
| 1431 | 197 | | } |
| | 198 | | } |