Skip to content

Commit 93f8d80

Browse files
author
LoneWandererProductions
committed
Improve a bit
1 parent a6678cf commit 93f8d80

6 files changed

Lines changed: 278 additions & 269 deletions

File tree

ExtendedSystemObjects/ExtendedList.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -253,11 +253,20 @@ public static bool Equal<TValue>(this List<TValue> lst, List<TValue> compare, En
253253
/// <returns>List split into chunks</returns>
254254
public static List<List<TValue>> ChunkBy<TValue>(this IEnumerable<TValue> source, int chunkSize)
255255
{
256-
return source
257-
.Select((x, i) => new { Index = i, Value = x })
258-
.GroupBy(x => x.Index / chunkSize)
259-
.Select(x => x.Select(v => v.Value).ToList())
260-
.ToList();
256+
var result = new List<List<TValue>>();
257+
var subList = new List<TValue>(chunkSize);
258+
259+
foreach (var item in source)
260+
{
261+
subList.Add(item);
262+
if (subList.Count == chunkSize)
263+
{
264+
result.Add(subList);
265+
subList = new List<TValue>(chunkSize);
266+
}
267+
}
268+
if (subList.Count > 0) result.Add(subList);
269+
return result;
261270
}
262271

263272
/// <summary>

ExtendedSystemObjects/ImmutableLookupMap.cs

Lines changed: 60 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using System;
1313
using System.Collections;
1414
using System.Collections.Generic;
15+
using System.Numerics;
1516
using System.Runtime.CompilerServices;
1617
using ExtendedSystemObjects.Helper;
1718

@@ -41,42 +42,63 @@ public sealed class ImmutableLookupMap<TKey, TValue> : IEnumerable<KeyValuePair<
4142

4243
/// <summary>
4344
/// Initializes a new instance of the <see cref="ImmutableLookupMap{TKey, TValue}" /> class.
45+
/// Uses Power-of-2 sizing and Linear Probing for O(1) lookups and high cache locality.
4446
/// </summary>
4547
/// <param name="data">A dictionary containing the key-value pairs to initialize the map.</param>
46-
/// <exception cref="ArgumentNullException">data</exception>
48+
/// <exception cref="ArgumentNullException">data is null.</exception>
49+
/// <exception cref="InvalidOperationException">Duplicate key detected.</exception>
4750
public ImmutableLookupMap(IDictionary<TKey, TValue> data)
4851
{
4952
ArgumentNullException.ThrowIfNull(data);
5053

51-
// Double the capacity and find the next prime number
52-
var capacity = FindNextPrime(data.Count * 2);
54+
// 1. Calculate capacity as a Power of 2 (at least double the count to keep load factor < 50%)
55+
int capacity = (int)BitOperations.RoundUpToPowerOf2((uint)data.Count * 2);
56+
if (capacity < 16) capacity = 16;
5357

54-
// Initialize the internal arrays
58+
// 2. The mask replaces the modulo operator: (hash % capacity) becomes (hash & mask)
59+
int mask = capacity - 1;
60+
61+
// 3. Initialize the internal arrays (Struct of Arrays approach)
5562
_keys = new TKey[capacity];
5663
_values = new TValue[capacity];
5764
_keyPresence = new bool[capacity];
5865

59-
// Populate the arrays with a brute-force quadratic approach
60-
foreach (var (key, value) in data)
66+
// 4. Populate the arrays using Linear Probing
67+
foreach (var kvp in data)
6168
{
62-
for (var i = 0; i < capacity; i++)
69+
TKey key = kvp.Key;
70+
TValue value = kvp.Value;
71+
72+
// Get initial hash index
73+
int hash = GetHash(key) & mask;
74+
bool placed = false;
75+
76+
for (int i = 0; i < capacity; i++)
6377
{
64-
var hash = (GetHash(key, capacity) + (i * i)) % capacity; // Quadratic probing formula
78+
// Linear probe: check the next slot
79+
int index = (hash + i) & mask;
6580

66-
if (!_keyPresence[hash])
81+
if (!_keyPresence[index])
6782
{
68-
_keys[hash] = key;
69-
_values[hash] = value;
70-
_keyPresence[hash] = true;
83+
_keys[index] = key;
84+
_values[index] = value;
85+
_keyPresence[index] = true;
86+
placed = true;
7187
break;
7288
}
7389

74-
if (_keys[hash].Equals(key))
90+
// Safety check for duplicate keys (since Dictionary allows them via different refs sometimes)
91+
if (_keys[index].Equals(key))
7592
{
76-
throw new InvalidOperationException(message: string.Format(SharedResources.ErrorDuplicateKey,
77-
key));
93+
throw new InvalidOperationException(string.Format(SharedResources.ErrorDuplicateKey, key));
7894
}
7995
}
96+
97+
if (!placed)
98+
{
99+
// This should be mathematically impossible with Linear Probing and Load Factor < 100%
100+
throw new InvalidOperationException("Internal map overflow.");
101+
}
80102
}
81103
}
82104

@@ -116,20 +138,20 @@ IEnumerator IEnumerable.GetEnumerator()
116138
/// <exception cref="KeyNotFoundException">Thrown if the key is not found in the map.</exception>
117139
public TValue Get(TKey key)
118140
{
119-
var hash = GetHash(key, _keys.Length);
120-
var originalHash = hash;
141+
var capacity = _keys.Length;
142+
var mask = capacity - 1;
143+
var hash = GetHash(key);
121144

122-
while (_keyPresence[hash])
145+
for (var i = 0; i < capacity; i++)
123146
{
124-
if (_keys[hash].Equals(key))
125-
{
126-
return _values[hash];
127-
}
147+
var index = (hash + i) & mask;
148+
149+
// If we hit an empty slot, the key definitely isn't here
150+
if (!_keyPresence[index]) break;
128151

129-
hash = (hash + 1) % _keys.Length; // Linear probing
130-
if (hash == originalHash)
152+
if (_keys[index].Equals(key))
131153
{
132-
break; // Full cycle, key not found
154+
return _values[index];
133155
}
134156
}
135157

@@ -147,21 +169,20 @@ public TValue Get(TKey key)
147169
/// <returns><c>true</c> if the key was found; otherwise, <c>false</c>.</returns>
148170
public bool TryGetValue(TKey key, out TValue value)
149171
{
150-
var hash = GetHash(key, _keys.Length);
151-
var originalHash = hash;
172+
var capacity = _keys.Length;
173+
var mask = capacity - 1;
174+
var hash = GetHash(key);
152175

153-
while (_keyPresence[hash])
176+
for (var i = 0; i < capacity; i++)
154177
{
155-
if (_keys[hash].Equals(key))
156-
{
157-
value = _values[hash];
158-
return true;
159-
}
178+
var index = (hash + i) & mask;
160179

161-
hash = (hash + 1) % _keys.Length; // Linear probing
162-
if (hash == originalHash)
180+
if (!_keyPresence[index]) break; // Hit an empty slot; key doesn't exist
181+
182+
if (_keys[index].Equals(key))
163183
{
164-
break; // Full cycle, key not found
184+
value = _values[index];
185+
return true;
165186
}
166187
}
167188

@@ -178,63 +199,10 @@ public bool TryGetValue(TKey key, out TValue value)
178199
/// <param name="capacity">The capacity.</param>
179200
/// <returns>Hash Value</returns>
180201
[MethodImpl(MethodImplOptions.AggressiveInlining)]
181-
private static int GetHash(TKey key, int capacity)
182-
{
183-
return Math.Abs(key.GetHashCode() % capacity);
184-
}
185-
186-
/// <summary>
187-
/// Finds the next prime.
188-
/// </summary>
189-
/// <param name="number">The number.</param>
190-
/// <returns>Next prime number</returns>
191-
private static int FindNextPrime(int number)
192-
{
193-
while (!IsPrime(number))
194-
{
195-
number++;
196-
}
197-
198-
return number;
199-
}
200-
201-
/// <summary>
202-
/// Determines whether the specified number is prime.
203-
/// Uses an internal dictionary for smaller Primes, to speed up the process.
204-
/// </summary>
205-
/// <param name="number">The number.</param>
206-
/// <returns>
207-
/// <c>true</c> if the specified number is prime; otherwise, <c>false</c>.
208-
/// </returns>
209-
private static bool IsPrime(int number)
202+
private static int GetHash(TKey key)
210203
{
211-
if (number < 2)
212-
{
213-
return false;
214-
}
215-
216-
foreach (var prime in SharedResources.SmallPrimes)
217-
{
218-
if (number == prime)
219-
{
220-
return true;
221-
}
222-
223-
if (number % prime == 0)
224-
{
225-
return false;
226-
}
227-
}
228-
229-
for (var i = 49; i * i <= number; i += 2)
230-
{
231-
if (number % i == 0)
232-
{
233-
return false;
234-
}
235-
}
236-
237-
return true;
204+
// Math.Abs is good to ensure we don't get negative indices before masking
205+
return Math.Abs(key.GetHashCode());
238206
}
239207
}
240208
}

0 commit comments

Comments
 (0)