1212using System ;
1313using System . Collections ;
1414using System . Collections . Generic ;
15+ using System . Numerics ;
1516using System . Runtime . CompilerServices ;
1617using 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