Skip to content

Commit 8699d07

Browse files
author
LoneWandererProductions
committed
Add last change tests and fixes
1 parent 0168ef0 commit 8699d07

3 files changed

Lines changed: 98 additions & 10 deletions

File tree

CommonExtendedObjectsTests/ImmutableLookupMapAllTests.cs

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,6 @@ public void TestReadOnlyDictionaryLookup()
139139
Trace.WriteLine($"ReadOnlyDictionary Lookup Time: {stopwatch.ElapsedMilliseconds} ms");
140140
}
141141

142-
// ------------ UNMANAGED MAP TESTS ----------------
143-
144142
/// <summary>
145143
/// Tests the unmanaged map initialization.
146144
/// </summary>
@@ -277,5 +275,93 @@ public void TestInitializationAndLookupPerformanceComparison()
277275
Assert.AreEqual(iterations, stringMap.ToList().Count);
278276
Assert.AreEqual(iterations, readOnlyMap.Count);
279277
}
278+
279+
/// <summary>
280+
/// Tests the collision robustness.
281+
/// </summary>
282+
[TestMethod]
283+
public void TestCollisionRobustness()
284+
{
285+
var data = new Dictionary<int, string>();
286+
// Using multiples of large numbers often creates hash clusters
287+
for (int i = 0; i < 100; i++)
288+
{
289+
data[i * 1024] = $"CollisionValue_{i}";
290+
}
291+
292+
var map = new ImmutableLookupMap<int, string>(data);
293+
294+
foreach (var key in data.Keys)
295+
{
296+
Assert.AreEqual(data[key], map.Get(key), $"Failed to find key {key} after potential collision.");
297+
}
298+
}
299+
300+
/// <summary>
301+
/// Tests the minimum capacity.
302+
/// </summary>
303+
[TestMethod]
304+
public void TestMinimumCapacity()
305+
{
306+
var data = new Dictionary<int, int> { { 1, 100 } };
307+
var map = new ImmutableLookupMap<int, int>(data);
308+
309+
Assert.AreEqual(100, map.Get(1));
310+
Assert.IsFalse(map.TryGetValue(2, out _));
311+
}
312+
313+
/// <summary>
314+
/// Tests the key not found throws correctly.
315+
/// </summary>
316+
[TestMethod]
317+
[ExpectedException(typeof(KeyNotFoundException))]
318+
public void TestKeyNotFoundThrowsCorrectly()
319+
{
320+
var data = new Dictionary<int, string> { { 10, "Ten" }, { 20, "Twenty" } };
321+
var map = new ImmutableLookupMap<int, string>(data);
322+
323+
// This should trigger the break in the probe loop and throw
324+
map.Get(99);
325+
}
326+
327+
/// <summary>
328+
/// Tests the negative keys.
329+
/// </summary>
330+
[TestMethod]
331+
public void TestNegativeKeys()
332+
{
333+
var data = new Dictionary<int, float>
334+
{
335+
{ -1, 1.1f },
336+
{ -500, 5.5f },
337+
{ int.MinValue, 0.0f }
338+
};
339+
340+
using var map = new ImmutableLookupMapUnmanaged<int, float>(data);
341+
342+
foreach (var kvp in data)
343+
{
344+
Assert.AreEqual(kvp.Value, map.Get(kvp.Key), $"Failed for negative key: {kvp.Key}");
345+
}
346+
}
347+
348+
/// <summary>
349+
/// Tests the sparse key spread.
350+
/// </summary>
351+
[TestMethod]
352+
public void TestSparseKeySpread()
353+
{
354+
var data = new Dictionary<int, string>
355+
{
356+
{ 1, "First" },
357+
{ 1_000_000, "Million" },
358+
{ int.MaxValue, "Max" }
359+
};
360+
361+
var map = new ImmutableLookupMap<int, string>(data);
362+
363+
Assert.AreEqual("Million", map.Get(1_000_000));
364+
Assert.AreEqual("Max", map.Get(int.MaxValue));
365+
}
280366
}
281367
}

ExtendedSystemObjects/ImmutableLookupMap.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using System.Collections.Generic;
1515
using System.Numerics;
1616
using System.Runtime.CompilerServices;
17+
using System.Threading.Tasks;
1718
using ExtendedSystemObjects.Helper;
1819

1920
namespace ExtendedSystemObjects
@@ -76,7 +77,7 @@ public ImmutableLookupMap(IDictionary<TKey, TValue> data)
7677
for (int i = 0; i < capacity; i++)
7778
{
7879
// Linear probe: check the next slot
79-
int index = (hash + i) & mask;
80+
int index = (int)((uint)(hash + i) & (uint)mask);
8081

8182
if (!_keyPresence[index])
8283
{
@@ -193,16 +194,17 @@ public bool TryGetValue(TKey key, out TValue value)
193194
// Helper Methods
194195

195196
/// <summary>
196-
/// Gets the hash.
197+
/// Gets the hash.
197198
/// </summary>
198199
/// <param name="key">The key.</param>
199-
/// <param name="capacity">The capacity.</param>
200-
/// <returns>Hash Value</returns>
200+
/// <returns>
201+
/// Hash Value
202+
/// </returns>
201203
[MethodImpl(MethodImplOptions.AggressiveInlining)]
202204
private static int GetHash(TKey key)
203205
{
204-
// Math.Abs is good to ensure we don't get negative indices before masking
205-
return Math.Abs(key.GetHashCode());
206+
// Return the raw hash code. Do NOT use Math.Abs.
207+
return key.GetHashCode();
206208
}
207209
}
208210
}

ExtendedSystemObjects/ImmutableLookupMapUnmanaged.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public ImmutableLookupMapUnmanaged(IDictionary<TKey, TValue> data)
8383
// 2. Linear Probing for cache line efficiency
8484
for (var i = 0; i < _capacity; i++)
8585
{
86-
int index = (hash + i) & _mask;
86+
int index = (int)((uint)(hash + i) & (uint)_mask);
8787
Entry* entry = entriesPtr + index;
8888

8989
if (entry->IsPresent == 0)
@@ -221,7 +221,7 @@ public bool TryGetValue(TKey key, out TValue value)
221221
[MethodImpl(MethodImplOptions.AggressiveInlining)]
222222
private static int GetHash(TKey key)
223223
{
224-
return Math.Abs(key.GetHashCode());
224+
return key.GetHashCode();
225225
}
226226
}
227227
}

0 commit comments

Comments
 (0)