Skip to content

Commit b4ead0f

Browse files
committed
Change to use array as backing storage
1 parent 8be3d6a commit b4ead0f

File tree

1 file changed

+109
-42
lines changed

1 file changed

+109
-42
lines changed

DictionaryList/DictionaryList.cs

Lines changed: 109 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,45 @@ namespace Vectorial1024.Collections.Generic
1212
/// </summary>
1313
public class DictionaryList<TValue> : IEnumerable<KeyValuePair<int,TValue>>
1414
{
15-
internal List<TValue> _list = new List<TValue>();
15+
// the version of the DictionaryList, to ensure the enumerator can work correctly
16+
private int _version;
1617

17-
internal bool[] _issetLookup;
18+
#region Backing Storage
19+
20+
private static readonly TValue[] EmptyArray = Array.Empty<TValue>();
21+
22+
private static readonly bool[] EmptyLookupArray = Array.Empty<bool>();
23+
24+
private const int DefaultCapacity = 4;
25+
26+
/// <summary>
27+
/// Actual usable capacity of the backing storage. Indexes smaller than this value are usable.
28+
/// <para/>
29+
/// Value increased by Add(); value decreased by CompactAndTrimExcess().
30+
/// </summary>
31+
internal int _size;
1832

1933
internal int _actualCount;
2034

21-
// the version of the DictionaryList, to ensure the enumerator can work correctly
22-
private int _version;
35+
/// <summary>
36+
/// The backing storage of this DictionaryList.
37+
/// </summary>
38+
internal TValue[] _items;
39+
40+
/// <summary>
41+
/// The index existence lookup array of this DictionaryList.
42+
/// </summary>
43+
internal bool[] _issetLookup;
44+
45+
#endregion
2346

2447
/// <summary>
2548
/// Initializes a new instance of the `DictionaryList&lt;TValue&gt;` class that is empty and has the default capacity.
2649
/// </summary>
2750
public DictionaryList()
2851
{
29-
var capacity = Capacity;
30-
_issetLookup = new bool[capacity];
52+
_items = EmptyArray;
53+
_issetLookup = EmptyLookupArray;
3154
}
3255

3356
/// <summary>
@@ -37,9 +60,10 @@ public DictionaryList()
3760
/// <param name="collection">The collection to take copies from.</param>
3861
public DictionaryList(DictionaryList<TValue> collection)
3962
{
40-
_list = new List<TValue>(collection._list);
63+
_items = (TValue[]) collection._items.Clone();
4164
_actualCount = collection._actualCount;
4265
_issetLookup = (bool[]) collection._issetLookup.Clone();
66+
_size = collection._size;
4367
}
4468

4569
/// <summary>
@@ -48,7 +72,7 @@ public DictionaryList(DictionaryList<TValue> collection)
4872
/// <param name="capacity"></param>
4973
public DictionaryList(int capacity)
5074
{
51-
_list = new List<TValue>(capacity);
75+
_items = new TValue[capacity];
5276
_issetLookup = new bool[capacity];
5377
}
5478

@@ -65,32 +89,37 @@ public DictionaryList(int capacity)
6589
/// Indexes that are unset are still counted towards the usage of the internal data structure.
6690
/// </summary>
6791
/// <seealso cref="CompactAndTrimExcess"/>
68-
public int Capacity => _list.Capacity;
92+
public int Capacity => _size;
6993

7094
public TValue this[int index]
7195
{
7296
get
7397
{
74-
var tempItem = _list[index];
98+
if ((uint)index >= (uint)_size)
99+
{
100+
// out of range!
101+
ThrowHelper.ThrowArgumentOutOfRangeException(index);
102+
}
103+
// definitely in range
75104
if (!IndexIsSet(index))
76105
{
77106
throw new KeyNotFoundException($"The given index {index} was unset in the list.");
78107
}
79-
return tempItem;
108+
return _items[index];
80109
}
81110
set
82111
{
83-
if ((uint)index >= (uint)_list.Count)
112+
if ((uint)index >= (uint)_size)
84113
{
85114
// out of range!
86115
ThrowHelper.ThrowArgumentOutOfRangeException(index);
87116
}
88-
if (!_issetLookup[index])
117+
if (!IndexIsSet(index))
89118
{
90119
// adding items during iteration is not allowed!
91120
_version++;
92121
}
93-
_list[index] = value;
122+
_items[index] = value;
94123
_issetLookup[index] = true;
95124
}
96125
}
@@ -102,17 +131,26 @@ public TValue this[int index]
102131
[MethodImpl(MethodImplOptions.AggressiveInlining)]
103132
public void Add(TValue value)
104133
{
105-
var nextIndex = _list.Count;
106-
_list.Add(value);
107-
_actualCount++;
108-
_version++;
109-
110-
// react to resizing if happened
111-
if (_issetLookup.Length != _list.Capacity)
134+
var nextIndex = _size;
135+
if (nextIndex == _items.Length)
112136
{
113-
Array.Resize(ref _issetLookup, _list.Capacity);
137+
// we need a larger backing storage
138+
GrowBackingStorage();
114139
}
140+
_items[nextIndex] = value;
115141
_issetLookup[nextIndex] = true;
142+
_actualCount++;
143+
_size++;
144+
_version++;
145+
}
146+
147+
private void GrowBackingStorage()
148+
{
149+
// what should be our next array length?
150+
var nextLength = _items.Length == 0 ? DefaultCapacity : _items.Length * 2;
151+
// then, grow it
152+
Array.Resize(ref _items, nextLength);
153+
Array.Resize(ref _issetLookup, nextLength);
116154
}
117155

118156
/// <summary>
@@ -124,12 +162,16 @@ public void Add(TValue value)
124162
/// <seealso cref="CompactAndTrimExcess"/>
125163
public void UnsetAt(int index)
126164
{
165+
if ((uint)index >= (uint)_size)
166+
{
167+
ThrowHelper.ThrowArgumentOutOfRangeException(index);
168+
}
127169
if (!IndexIsSet(index))
128170
{
129171
return;
130172
}
131173

132-
_list[index] = default!;
174+
_items[index] = default!;
133175
_actualCount--;
134176
_issetLookup[index] = false;
135177
}
@@ -141,12 +183,7 @@ public void UnsetAt(int index)
141183
/// <returns>Returns true if the index is in use by an element; false otherwise (e.g. unused memory left behind by RemoveAt())</returns>
142184
public bool ContainsIndex(int index)
143185
{
144-
if (index < 0)
145-
{
146-
return false;
147-
}
148-
149-
if (index > _list.Count)
186+
if ((uint)index >= (uint)_size)
150187
{
151188
return false;
152189
}
@@ -169,9 +206,23 @@ internal bool IndexIsSet(int index)
169206
/// </summary>
170207
public void Clear()
171208
{
172-
_list.Clear();
209+
_version++;
210+
var oldSize = _size;
211+
if (RuntimeHelpers.IsReferenceOrContainsReferences<TValue>())
212+
{
213+
// Clear the elements so that the gc can reclaim the references.
214+
_size = 0;
215+
if (oldSize > 0)
216+
{
217+
Array.Clear(_items, 0, oldSize);
218+
}
219+
}
220+
else
221+
{
222+
_size = 0;
223+
}
173224
_actualCount = 0;
174-
_issetLookup = new bool[Capacity];
225+
Array.Clear(_issetLookup, 0, oldSize);
175226
}
176227

177228
/// <summary>
@@ -181,19 +232,39 @@ public void Clear()
181232
/// </summary>
182233
public void CompactAndTrimExcess()
183234
{
184-
var newList = new List<TValue>(_actualCount);
185-
for (var index = 0; index < _list.Count; index++)
235+
_version++;
236+
if (_actualCount == 0)
237+
{
238+
// somehow reset this
239+
_items = EmptyArray;
240+
_issetLookup = EmptyLookupArray;
241+
_size = 0;
242+
return;
243+
}
244+
245+
// prepare the new backing storage
246+
var newItems = new TValue[_actualCount];
247+
var newIndex = 0;
248+
for (var index = 0; index < _size; index++)
186249
{
187250
if (!IndexIsSet(index))
188251
{
252+
// this is an empty slot; skip
189253
continue;
190254
}
191-
newList.Add(_list[index]);
255+
// this has items; move it over!
256+
newItems[newIndex] = _items[index];
257+
newIndex++;
258+
if (newIndex == _actualCount)
259+
{
260+
// we have moved everything; early finish!
261+
break;
262+
}
192263
}
193-
_list = newList;
194-
_actualCount = newList.Count;
195-
_version++;
264+
265+
_items = newItems;
196266
_issetLookup = Enumerable.Repeat(true, _actualCount).ToArray();
267+
_size = _actualCount;
197268
}
198269

199270
#endregion
@@ -205,25 +276,21 @@ internal struct DictionaryListEnumerator : IEnumerator<KeyValuePair<int, TValue>
205276
private readonly DictionaryList<TValue> _dictList;
206277
private int _index;
207278
private KeyValuePair<int, TValue> _current;
208-
private readonly int _size;
209279
private readonly int _version;
210-
private readonly bool[] _issetLookup;
211280

212281
internal DictionaryListEnumerator(DictionaryList<TValue> dictList)
213282
{
214283
_index = 0;
215284
_dictList = dictList;
216285
_current = default;
217-
_size = _dictList._list.Count;
218286
_version = dictList._version;
219-
_issetLookup = _dictList._issetLookup;
220287
}
221288

222289
public bool MoveNext()
223290
{
224291
var iterIndex = _index;
225292
var theDictList = _dictList;
226-
while (_version == theDictList._version && (uint)iterIndex < (uint)_size)
293+
while (_version == theDictList._version && (uint)iterIndex < (uint)theDictList._size)
227294
{
228295
if (!theDictList.IndexIsSet(iterIndex))
229296
{

0 commit comments

Comments
 (0)