UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
HashTable.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
7#include "uLang/Common/Templates/Storage.h" // for Swap()
9
10#include <iterator>
11#include <algorithm>
12
13namespace uLang
14{
15
16template<class KeyType, class ValueType>
18{
19 KeyType _Key;
20 ValueType _Value;
21
22 bool operator==(const KeyType& Key) const { return _Key == Key; }
23 operator const KeyType&() const { return _Key; }
24};
25
29template<class KeyType, class KeyValueType, class HashTraits, class AllocatorType, typename... AllocatorArgsType>
31{
32public:
37
38 THashTable(const THashTable& Other) = delete;
39
44
46 {
47 Swap(Other);
48 return *this;
49 }
50
52 {
53 uLang::Swap(_Entries, Other._Entries);
54 uLang::Swap(_NumEntries, Other._NumEntries);
55 uLang::Swap(_NumOccupied, Other._NumOccupied);
56 uLang::Swap(_Allocator, Other._Allocator);
57 }
58
60 {
61 Empty();
62 if (_Entries)
63 {
64 _Allocator.Deallocate(_Entries);
65 }
66 }
67
69 {
70 return _NumOccupied;
71 }
72
73 ULANG_FORCEINLINE bool Contains(const KeyType& Key) const
74 {
75 return Lookup(Key) != uint32_t(IndexNone);
76 }
77
78 ULANG_FORCEINLINE KeyValueType* Find(const KeyType& Key)
79 {
80 uint32_t Pos = Lookup(Key);
81 return Pos == uint32_t(IndexNone) ? nullptr : &_Entries[Pos]._KeyValue;
82 }
83
84 ULANG_FORCEINLINE const KeyValueType* Find(const KeyType& Key) const
85 {
86 uint32_t Pos = Lookup(Key);
87 return Pos == uint32_t(IndexNone) ? nullptr : &_Entries[Pos]._KeyValue;
88 }
89
98 template <typename Predicate>
99 const KeyValueType* FindByPredicate(Predicate Pred) const
100 {
101 for (uint32_t Index = 0; Index < _NumEntries; ++Index)
102 {
103 // If the hash of the entry is `0`, it means that the slot is currently unoccupied.
104 if (_Entries[Index]._Hash != 0 && Pred(_Entries[Index]._KeyValue))
105 {
106 return &_Entries[Index]._KeyValue;
107 }
108 }
109 return nullptr;
110 }
111
120 template <typename Predicate>
121 KeyValueType* FindByPredicate(Predicate Pred)
122 {
123 for (uint32_t Index = 0; Index < _NumEntries; ++Index)
124 {
125 // If the hash of the entry is `0`, it means that the slot is currently unoccupied.
126 if (_Entries[Index]._Hash != 0 && Pred(_Entries[Index]._KeyValue))
127 {
128 return &_Entries[Index]._KeyValue;
129 }
130 }
131 return nullptr;
132 }
133
134 KeyValueType& Insert(const KeyValueType& KeyValue)
135 {
136 return InsertInternal(KeyValueType(KeyValue));
137 }
138
139 KeyValueType& Insert(KeyValueType&& KeyValue)
140 {
142 }
143
144 KeyValueType& FindOrInsert(KeyValueType&& KeyValue)
145 {
146 if (KeyValueType* Entry = Find(KeyValue))
147 {
148 return *Entry;
149 }
150 else
151 {
152 return Insert(Move(KeyValue));
153 }
154 }
155
156 bool Remove(const KeyType& Key)
157 {
158 auto MoveAndDestruct = [](SEntry& Dest, SEntry& Source)
159 {
160 new (&Dest) SEntry(Move(Source));
161 Source.~SEntry();
162 };
163
164 uint32_t Pos = Lookup(Key);
165 if (Pos == uint32_t(IndexNone))
166 {
167 return false;
168 }
169
170 // Backward shift deletion
171 // Idea is to shift backward all the entries following the entry to delete until either a vacant entry, or a entry with a probe distance of 0 is found.
172 // By doing this, every deletion will shift backwards entries and therefore decrease their respective probe distances by 1.
173 // An intuitive way to understand the backward shift is to think that by shifting backward the entries, the table is left as if the deleted entry had never been inserted.
174 // This is why even after a large number of deletions, the mean probe distance and the variance of the probe distance remain constant and low.
175
176 // Destruct entry's key/value
177 _Entries[Pos]._KeyValue.~KeyValueType();
178
179 // Linear probe to find stop entry
180 uint32_t StopPos = (Pos + 1) & (_NumEntries - 1);
181 for (;;)
182 {
183 SEntry& Entry = _Entries[StopPos];
184 if (Entry._Hash == 0 || ProbeDistance(Entry._Hash, StopPos) == 0)
185 {
186 break;
187 }
188
189 StopPos = (StopPos + 1) & (_NumEntries - 1);
190 }
191
192 // Shift entries backward to fill the hole and reduce their probe distance
193 if (Pos < StopPos)
194 {
195 for (uint32_t Index = Pos; Index + 1 < StopPos; ++Index)
196 {
198 }
199 }
200 else
201 {
202 // Pos wrapped around, do the move in chunks
203 for (uint32_t Index = Pos; Index + 1 < _NumEntries; ++Index)
204 {
206 }
207 if (StopPos > 0)
208 {
210 for (uint32_t Index = 0; Index + 1 < StopPos; ++Index)
211 {
213 }
214 }
215 }
216
217 // Mark the hole we left behind as vacant
218 _Entries[(StopPos + _NumEntries - 1) & (_NumEntries - 1)]._Hash = 0;
219
220 // Contains one less now
221 --_NumOccupied;
222
223 return true;
224 }
225
226 bool IsEmpty() const
227 {
228 return _NumOccupied == 0;
229 }
230
231 void Empty()
232 {
233 if (_Entries)
234 {
235 for (uint32_t Pos = 0; Pos < _NumEntries; ++Pos)
236 {
237 SEntry& Entry = _Entries[Pos];
238 if (Entry._Hash)
239 {
240 Entry._KeyValue.~KeyValueType();
241 }
242 }
243 }
244 _NumOccupied = 0;
245 _NumEntries = 0;
246 }
247
248 // NOTE: (yiliang.siew) We're doing it without concepts/constraints since we still do not compile against C++20 for Unreal at the
249 // moment and we need to support that.
251protected:
252 struct SEntry;
253
254public:
255 template <bool bConst>
257 {
258 public:
259 using iterator_category = std::forward_iterator_tag;
260 using value_type = KeyValueType;
261 using pointer = typename std::conditional_t<bConst, const KeyValueType*, KeyValueType*>;
262 using reference = typename std::conditional_t<bConst, const KeyValueType&, KeyValueType&>;
263
264 private:
265 explicit Iterator(SEntry* InEntry, SEntry* InEnd) : _CurrentEntry(InEntry), _End(InEnd)
266 {
267 EnsureOccupiedOrEnd();
268 }
269
270 public:
271 // Prefix increment overload.
273 {
274 if (_CurrentEntry < _End)
275 {
276 ++_CurrentEntry;
277 EnsureOccupiedOrEnd();
278 }
279 return *this;
280 }
281
282 // Postfix increment overload.
284 {
285 Iterator tmp = *this;
286 ++(*this);
287 return tmp;
288 }
289
291 {
292 ULANG_ASSERTF(_End == Other._End, "Iterator ends were mismatched!");
293 return _CurrentEntry != Other._CurrentEntry;
294 }
295
297 {
298 return !(*this != Other);
299 }
300
301 template <bool _bConst = bConst>
302 ULANG_FORCEINLINE std::enable_if_t<!_bConst, reference> operator*()
303 {
304 return _CurrentEntry->_KeyValue;
305 }
306
307 template <bool _bConst = bConst>
308 ULANG_FORCEINLINE std::enable_if_t<_bConst, reference> operator*() const
309 {
310 return _CurrentEntry->_KeyValue;
311 }
312
313 template <bool _bConst = bConst>
314 ULANG_FORCEINLINE std::enable_if_t<!_bConst, pointer> operator->()
315 {
316 return _CurrentEntry->_KeyValue;
317 }
318
319 template <bool _bConst = bConst>
320 ULANG_FORCEINLINE std::enable_if_t<_bConst, pointer> operator->() const
321 {
322 return _CurrentEntry->_KeyValue;
323 }
324
325 private:
326 void EnsureOccupiedOrEnd()
327 {
328 // If the hash is `0`, it means that the space in the hash table is currently free/unused.
330 {
331 ++_CurrentEntry;
332 }
333 }
334
335 SEntry* _CurrentEntry;
336 SEntry* _End;
337
338 friend class THashTable;
339 };
340
342 {
343 if (_Entries != nullptr && _NumEntries != 0)
344 {
346 }
347 return end();
348 }
349
354
356 {
357 return cbegin();
358 }
359
361 {
362 return cend();
363 }
364
366 {
367 if (_Entries != nullptr && _NumEntries != 0)
368 {
370 }
371 return cend();
372 }
373
378
379protected:
380 // Load factor = what fraction of entries are occupied
381 static constexpr uint64_t MaxLoadFactorNumerator = 7;
382 static constexpr uint64_t MaxLoadFactorDenominator = 8;
383
384 struct SEntry
385 {
386 uint32_t _Hash; // 0 means unused
387 KeyValueType _KeyValue; // Some value, e.g. index into some external array
388 };
389
390 // Hash values in the table must not be zero
392 {
393 uint32_t Hash = HashTraits::GetKeyHash(Key);
394 return Hash + uint32_t(Hash == 0);
395 }
396
397 // Get desired position for an entry given a hash value
399 {
400 return Hash & (_NumEntries - 1);
401 }
402
403 // Get probe distance of an entry with given hash and position in entry array
405 {
406 return (Pos + _NumEntries - Hash) & (_NumEntries - 1);
407 }
408
409 KeyValueType& InsertInternal(KeyValueType&& KeyValue)
410 {
412 {
413 Grow();
414 }
415 bool bAlreadyExists{};
417 if (!bAlreadyExists)
418 {
419 ++_NumOccupied;
420 }
421 return _Entries[NewPos]._KeyValue;
422 }
423
424 // Create a new entry
425 // Use Robin Hood mechanism to rearrange entries to minimize probe distance
426 // Returns position of new entry
428 {
429 uint32_t Pos = DesiredPos(Hash);
430 uint32_t Distance = 0;
431 bool bPreviouslyInserted = false;
433 for (;;)
434 {
435 SEntry& Entry = _Entries[Pos];
436 // If the entry already exists, just return it.
437 if (Entry._Hash == Hash)
438 {
439 const KeyType& Key = Value; // Make sure we are looking at just the key, not the value
440 if (Entry._KeyValue == Key) // Check for equality of key regardless of value
441 {
443 if (bAlreadyExists)
444 {
445 *bAlreadyExists = true;
446 }
447 if (!TAreTypesEqual<KeyType, KeyValueType>::Value) // Equality could be stored in the template as constexpr bool bIsSet
448 {
449 Entry._KeyValue = Move(Value); // Update value as it might be different
450 }
451 return Pos;
452 }
453 }
454
455 if (Entry._Hash == 0)
456 {
457 Entry._Hash = Hash;
458 new (&Entry._KeyValue) KeyValueType(Move(Value));
459 return bPreviouslyInserted ? InsertedPos : Pos;
460 }
461
462 // If the existing element has a shorter probe distance, swap places and keep going
463 // I.e. we are maintaining the following invariant:
464 // Given a hash, all entries from its desired pos to the last entry with that hash have a larger or equal probe distance than their probe distance with regard to the hash
467 {
469 {
470 bPreviouslyInserted = true;
471 InsertedPos = Pos;
472 }
473 uLang::Swap(Entry._Hash, Hash);
476 }
477
478 Pos = DesiredPos(Pos + 1);
479 ++Distance;
480 }
481 }
482
483 // Look up a key, return its index
484 ULANG_FORCEINLINE uint32_t Lookup(const KeyType& Key) const
485 {
486 if (!_NumEntries)
487 {
488 return uint32_t(IndexNone);
489 }
490
492 uint32_t Pos = DesiredPos(Hash);
493 uint32_t Distance = 0;
494 for (;;)
495 {
496 const SEntry& Entry = _Entries[Pos];
497 // We know that when we probe an element during insertion, the one with the longer probe distance of the two gets to keep the slot.
498 // So if we're looking for an element that exists, we should expect to never see an existing element with a shorter probe distance
499 // than our current distance (if that had happened, there would have been a swap during insertion!).
500 if (Entry._Hash == 0 || Distance > ProbeDistance(Entry._Hash, Pos))
501 {
502 return uint32_t(IndexNone);
503 }
504 // 32-bit hashes, if computed right, are pretty darn unique, so the chance that the hash matches but the key doesn't is tiny (2^-32)
505 // That means that if the hash matches, the key will practically always match as well
506 // So we really only ever have to do at most one real key comparison per lookup
507 if (Entry._Hash == Hash && Entry._KeyValue == Key)
508 {
509 return Pos;
510 }
511
512 Pos = (Pos + 1) & (_NumEntries - 1);
513 ++Distance;
514 }
515 }
516
517 // Allocate memory according to currently set _NumEntries
518 void Allocate()
519 {
520 if (_NumEntries)
521 {
522 ULANG_ASSERTF(CMath::IsPowerOf2(_NumEntries), "_NumEntries must be a power of 2.");
523
524 size_t BytesToAllocate = _NumEntries * sizeof(SEntry);
526 for (uint32_t Pos = 0; Pos < _NumEntries; ++Pos)
527 {
528 _Entries[Pos]._Hash = 0;
529 }
530 }
531 }
532
533 // Double the size of the table
534 void Grow()
535 {
539 Allocate();
540 if (PrevNumEntries)
541 {
542 for (uint32_t Pos = 0; Pos < PrevNumEntries; ++Pos)
543 {
546 if (Hash)
547 {
548 InsertInternal(Hash, Move(PrevEntry._KeyValue), nullptr);
549 PrevEntry._KeyValue.~KeyValueType();
550 }
551 }
552 _Allocator.Deallocate(PrevEntries);
553 }
554 }
555
557 uint32_t _NumEntries{}; // How many entries we have allocated in total
558 uint32_t _NumOccupied{}; // How many entries are actually occupied
559
562 AllocatorType _Allocator;
563};
564}
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
#define ULANG_ASSERT(expr)
Definition Common.h:285
#define ULANG_FORCEINLINE
Definition Common.h:188
#define ULANG_ASSERTF(expr, format,...)
Definition Common.h:290
static ULANG_FORCEINLINE constexpr bool IsPowerOf2(const T X)
Checks if a number is a power of two.
Definition MathUtils.h:40
Definition HashTable.h:257
ULANG_FORCEINLINE Iterator & operator++()
Definition HashTable.h:272
ULANG_FORCEINLINE Iterator operator++(int)
Definition HashTable.h:283
ULANG_FORCEINLINE bool operator!=(const Iterator &Other) const
Definition HashTable.h:290
ULANG_FORCEINLINE bool operator==(const Iterator &Other) const
Definition HashTable.h:296
typename std::conditional_t< bConst, const KeyValueType &, KeyValueType & > reference
Definition HashTable.h:262
ULANG_FORCEINLINE std::enable_if_t<!_bConst, reference > operator*()
Definition HashTable.h:302
ULANG_FORCEINLINE std::enable_if_t<!_bConst, pointer > operator->()
Definition HashTable.h:314
ULANG_FORCEINLINE std::enable_if_t< _bConst, pointer > operator->() const
Definition HashTable.h:320
KeyValueType value_type
Definition HashTable.h:260
typename std::conditional_t< bConst, const KeyValueType *, KeyValueType * > pointer
Definition HashTable.h:261
std::forward_iterator_tag iterator_category
Definition HashTable.h:259
ULANG_FORCEINLINE std::enable_if_t< _bConst, reference > operator*() const
Definition HashTable.h:308
Definition HashTable.h:31
static ULANG_FORCEINLINE uint32_t ComputeNonZeroHash(const KeyType &Key)
Definition HashTable.h:391
KeyValueType & InsertInternal(KeyValueType &&KeyValue)
Definition HashTable.h:409
ULANG_FORCEINLINE uint32_t Num() const
Definition HashTable.h:68
ULANG_FORCEINLINE Iterator< false > end() const
Definition HashTable.h:360
ULANG_FORCEINLINE Iterator< true > cend() const
Definition HashTable.h:374
THashTable & operator=(THashTable Other)
Definition HashTable.h:45
KeyValueType & Insert(const KeyValueType &KeyValue)
Definition HashTable.h:134
~THashTable()
Definition HashTable.h:59
THashTable(AllocatorArgsType &&... AllocatorArgs)
Definition HashTable.h:33
KeyValueType * FindByPredicate(Predicate Pred)
Definition HashTable.h:121
ULANG_FORCEINLINE Iterator< true > cbegin() const
Definition HashTable.h:365
SEntry * _Entries
Definition HashTable.h:556
ULANG_FORCEINLINE uint32_t Lookup(const KeyType &Key) const
Definition HashTable.h:484
static constexpr uint64_t MaxLoadFactorNumerator
Definition HashTable.h:381
KeyValueType & FindOrInsert(KeyValueType &&KeyValue)
Definition HashTable.h:144
ULANG_FORCEINLINE bool Contains(const KeyType &Key) const
Definition HashTable.h:73
void Empty()
Definition HashTable.h:231
THashTable(THashTable &&Other)
Definition HashTable.h:40
ULANG_FORCEINLINE Iterator< true > begin() const
Definition HashTable.h:355
uint32_t _NumEntries
Definition HashTable.h:557
ULANG_FORCEINLINE Iterator< false > end()
Definition HashTable.h:350
void Grow()
Definition HashTable.h:534
void Swap(THashTable &Other)
Definition HashTable.h:51
THashTable(const THashTable &Other)=delete
AllocatorType _Allocator
Definition HashTable.h:562
uint32_t _NumOccupied
Definition HashTable.h:558
ULANG_FORCEINLINE Iterator< false > begin()
Definition HashTable.h:341
uint32_t InsertInternal(uint32_t Hash, KeyValueType &&Value, bool *bAlreadyExists)
Definition HashTable.h:427
ULANG_FORCEINLINE uint32_t DesiredPos(uint32_t Hash) const
Definition HashTable.h:398
void Allocate()
Definition HashTable.h:518
ULANG_FORCEINLINE KeyValueType * Find(const KeyType &Key)
Definition HashTable.h:78
bool IsEmpty() const
Definition HashTable.h:226
bool Remove(const KeyType &Key)
Definition HashTable.h:156
ULANG_FORCEINLINE uint32_t ProbeDistance(uint32_t Hash, uint32_t Pos) const
Definition HashTable.h:404
ULANG_FORCEINLINE const KeyValueType * Find(const KeyType &Key) const
Definition HashTable.h:84
const KeyValueType * FindByPredicate(Predicate Pred) const
Definition HashTable.h:99
static constexpr uint64_t MaxLoadFactorDenominator
Definition HashTable.h:382
KeyValueType & Insert(KeyValueType &&KeyValue)
Definition HashTable.h:139
Definition VVMEngineEnvironment.h:23
@ IndexNone
Definition Common.h:381
ULANG_FORCEINLINE T && ForwardArg(typename TRemoveReference< T >::Type &Obj)
Definition References.h:115
TEnableIf< TUseBitwiseSwap< T >::Value >::Type Swap(T &A, T &B)
Definition Storage.h:138
ULANG_FORCEINLINE TRemoveReference< T >::Type && Move(T &&Obj)
Definition References.h:86
U16 Index
Definition radfft.cpp:71
Definition TypeTraits.h:36
Definition HashTable.h:385
KeyValueType _KeyValue
Definition HashTable.h:387
uint32_t _Hash
Definition HashTable.h:386
Definition HashTable.h:18
bool operator==(const KeyType &Key) const
Definition HashTable.h:22
ValueType _Value
Definition HashTable.h:20
KeyType _Key
Definition HashTable.h:19