Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/core/templates/hash_set.h
21121 views
1
/**************************************************************************/
2
/* hash_set.h */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#pragma once
32
33
#include "core/os/memory.h"
34
#include "core/string/print_string.h"
35
#include "core/templates/hashfuncs.h"
36
37
/**
38
* Implementation of Set using a bidi indexed hash map.
39
* Use RBSet instead of this only if the following conditions are met:
40
*
41
* - You need to keep an iterator or const pointer to Key and you intend to add/remove elements in the meantime.
42
* - Iteration order does matter (via operator<)
43
*
44
*/
45
46
template <typename TKey,
47
typename Hasher = HashMapHasherDefault,
48
typename Comparator = HashMapComparatorDefault<TKey>>
49
class HashSet {
50
public:
51
static constexpr uint32_t MIN_CAPACITY_INDEX = 2; // Use a prime.
52
static constexpr float MAX_OCCUPANCY = 0.75;
53
static constexpr uint32_t EMPTY_HASH = 0;
54
55
private:
56
TKey *_keys = nullptr;
57
uint32_t *_hash_idx_to_key_idx = nullptr;
58
uint32_t *_key_idx_to_hash_idx = nullptr;
59
uint32_t *_hashes = nullptr;
60
61
uint32_t _capacity_idx = 0;
62
uint32_t _size = 0;
63
64
_FORCE_INLINE_ uint32_t _hash(const TKey &p_key) const {
65
uint32_t hash = Hasher::hash(p_key);
66
67
if (unlikely(hash == EMPTY_HASH)) {
68
hash = EMPTY_HASH + 1;
69
}
70
71
return hash;
72
}
73
74
_FORCE_INLINE_ static constexpr void _increment_mod(uint32_t &r_idx, const uint32_t p_capacity) {
75
r_idx++;
76
// `if` is faster than both fastmod and mod.
77
if (unlikely(r_idx == p_capacity)) {
78
r_idx = 0;
79
}
80
}
81
82
static _FORCE_INLINE_ uint32_t _get_probe_length(const uint32_t p_hash_idx, const uint32_t p_hash, const uint32_t p_capacity, const uint64_t p_capacity_inv) {
83
const uint32_t original_idx = fastmod(p_hash, p_capacity_inv, p_capacity);
84
const uint32_t distance_idx = p_hash_idx - original_idx + p_capacity;
85
// At most p_capacity over 0, so we can use an if (faster than fastmod).
86
return distance_idx >= p_capacity ? distance_idx - p_capacity : distance_idx;
87
}
88
89
bool _lookup_key_idx(const TKey &p_key, uint32_t &r_key_idx) const {
90
if (_keys == nullptr || _size == 0) {
91
return false; // Failed lookups, no elements
92
}
93
94
const uint32_t capacity = hash_table_size_primes[_capacity_idx];
95
const uint64_t capacity_inv = hash_table_size_primes_inv[_capacity_idx];
96
uint32_t hash = _hash(p_key);
97
uint32_t hash_idx = fastmod(hash, capacity_inv, capacity);
98
uint32_t distance = 0;
99
100
while (true) {
101
if (_hashes[hash_idx] == EMPTY_HASH) {
102
return false;
103
}
104
105
if (_hashes[hash_idx] == hash && Comparator::compare(_keys[_hash_idx_to_key_idx[hash_idx]], p_key)) {
106
r_key_idx = _hash_idx_to_key_idx[hash_idx];
107
return true;
108
}
109
110
if (distance > _get_probe_length(hash_idx, _hashes[hash_idx], capacity, capacity_inv)) {
111
return false;
112
}
113
114
_increment_mod(hash_idx, capacity);
115
distance++;
116
}
117
}
118
119
uint32_t _insert_with_hash(uint32_t p_hash, uint32_t p_key_idx) {
120
const uint32_t capacity = hash_table_size_primes[_capacity_idx];
121
const uint64_t capacity_inv = hash_table_size_primes_inv[_capacity_idx];
122
uint32_t hash = p_hash;
123
uint32_t key_idx = p_key_idx;
124
uint32_t distance = 0;
125
uint32_t hash_idx = fastmod(hash, capacity_inv, capacity);
126
127
while (true) {
128
if (_hashes[hash_idx] == EMPTY_HASH) {
129
_hashes[hash_idx] = hash;
130
_key_idx_to_hash_idx[key_idx] = hash_idx;
131
_hash_idx_to_key_idx[hash_idx] = key_idx;
132
return hash_idx;
133
}
134
135
// Not an empty slot, let's check the probing length of the existing one.
136
uint32_t existing_probe_len = _get_probe_length(hash_idx, _hashes[hash_idx], capacity, capacity_inv);
137
if (existing_probe_len < distance) {
138
_key_idx_to_hash_idx[key_idx] = hash_idx;
139
SWAP(hash, _hashes[hash_idx]);
140
SWAP(key_idx, _hash_idx_to_key_idx[hash_idx]);
141
distance = existing_probe_len;
142
}
143
144
_increment_mod(hash_idx, capacity);
145
distance++;
146
}
147
}
148
149
void _resize_and_rehash(uint32_t p_new_capacity_idx) {
150
// Capacity can't be 0.
151
_capacity_idx = MAX((uint32_t)MIN_CAPACITY_INDEX, p_new_capacity_idx);
152
153
uint32_t capacity = hash_table_size_primes[_capacity_idx];
154
155
uint32_t *old_hashes = _hashes;
156
uint32_t *old_key_to_hash = _key_idx_to_hash_idx;
157
158
static_assert(EMPTY_HASH == 0, "Assuming EMPTY_HASH = 0 for alloc_static_zeroed call");
159
_hashes = reinterpret_cast<uint32_t *>(Memory::alloc_static_zeroed(sizeof(uint32_t) * capacity));
160
_keys = reinterpret_cast<TKey *>(Memory::realloc_static(_keys, sizeof(TKey) * capacity));
161
_key_idx_to_hash_idx = reinterpret_cast<uint32_t *>(Memory::alloc_static(sizeof(uint32_t) * capacity));
162
_hash_idx_to_key_idx = reinterpret_cast<uint32_t *>(Memory::realloc_static(_hash_idx_to_key_idx, sizeof(uint32_t) * capacity));
163
164
for (uint32_t i = 0; i < _size; i++) {
165
uint32_t h = old_hashes[old_key_to_hash[i]];
166
_insert_with_hash(h, i);
167
}
168
169
Memory::free_static(old_hashes);
170
Memory::free_static(old_key_to_hash);
171
}
172
173
// Returns key index.
174
_FORCE_INLINE_ int32_t _insert(const TKey &p_key) {
175
uint32_t capacity = hash_table_size_primes[_capacity_idx];
176
if (unlikely(_keys == nullptr)) {
177
// Allocate on demand to save memory.
178
179
static_assert(EMPTY_HASH == 0, "Assuming EMPTY_HASH = 0 for alloc_static_zeroed call");
180
_hashes = reinterpret_cast<uint32_t *>(Memory::alloc_static_zeroed(sizeof(uint32_t) * capacity));
181
_keys = reinterpret_cast<TKey *>(Memory::alloc_static(sizeof(TKey) * capacity));
182
_key_idx_to_hash_idx = reinterpret_cast<uint32_t *>(Memory::alloc_static(sizeof(uint32_t) * capacity));
183
_hash_idx_to_key_idx = reinterpret_cast<uint32_t *>(Memory::alloc_static(sizeof(uint32_t) * capacity));
184
}
185
186
uint32_t key_idx = 0;
187
bool exists = _lookup_key_idx(p_key, key_idx);
188
189
if (exists) {
190
return key_idx;
191
} else {
192
if (_size + 1 > MAX_OCCUPANCY * capacity) {
193
ERR_FAIL_COND_V_MSG(_capacity_idx + 1 == HASH_TABLE_SIZE_MAX, -1, "Hash table maximum capacity reached, aborting insertion.");
194
_resize_and_rehash(_capacity_idx + 1);
195
}
196
197
uint32_t hash = _hash(p_key);
198
memnew_placement(&_keys[_size], TKey(p_key));
199
_insert_with_hash(hash, _size);
200
_size++;
201
return _size - 1;
202
}
203
}
204
205
void _init_from(const HashSet &p_other) {
206
_capacity_idx = p_other._capacity_idx;
207
_size = p_other._size;
208
209
if (p_other._size == 0) {
210
return;
211
}
212
213
uint32_t capacity = hash_table_size_primes[_capacity_idx];
214
215
_hashes = reinterpret_cast<uint32_t *>(Memory::alloc_static(sizeof(uint32_t) * capacity));
216
_keys = reinterpret_cast<TKey *>(Memory::alloc_static(sizeof(TKey) * capacity));
217
_key_idx_to_hash_idx = reinterpret_cast<uint32_t *>(Memory::alloc_static(sizeof(uint32_t) * capacity));
218
_hash_idx_to_key_idx = reinterpret_cast<uint32_t *>(Memory::alloc_static(sizeof(uint32_t) * capacity));
219
220
for (uint32_t i = 0; i < _size; i++) {
221
memnew_placement(&_keys[i], TKey(p_other._keys[i]));
222
_key_idx_to_hash_idx[i] = p_other._key_idx_to_hash_idx[i];
223
}
224
225
for (uint32_t i = 0; i < capacity; i++) {
226
_hashes[i] = p_other._hashes[i];
227
_hash_idx_to_key_idx[i] = p_other._hash_idx_to_key_idx[i];
228
}
229
}
230
231
public:
232
_FORCE_INLINE_ uint32_t get_capacity() const { return hash_table_size_primes[_capacity_idx]; }
233
_FORCE_INLINE_ uint32_t size() const { return _size; }
234
235
/* Standard Godot Container API */
236
237
bool is_empty() const {
238
return _size == 0;
239
}
240
241
void clear() {
242
if (_keys == nullptr || _size == 0) {
243
return;
244
}
245
246
uint32_t capacity = hash_table_size_primes[_capacity_idx];
247
memset(_hashes, EMPTY_HASH, sizeof(EMPTY_HASH) * capacity);
248
249
if constexpr (!std::is_trivially_destructible_v<TKey>) {
250
for (uint32_t i = 0; i < _size; i++) {
251
_keys[i].~TKey();
252
}
253
}
254
255
_size = 0;
256
}
257
258
_FORCE_INLINE_ bool has(const TKey &p_key) const {
259
uint32_t _idx = 0;
260
return _lookup_key_idx(p_key, _idx);
261
}
262
263
bool erase(const TKey &p_key) {
264
uint32_t key_idx = 0;
265
bool exists = _lookup_key_idx(p_key, key_idx);
266
267
if (!exists) {
268
return false;
269
}
270
271
uint32_t hash_idx = _key_idx_to_hash_idx[key_idx];
272
273
const uint32_t capacity = hash_table_size_primes[_capacity_idx];
274
const uint64_t capacity_inv = hash_table_size_primes_inv[_capacity_idx];
275
uint32_t next_hash_idx = fastmod(hash_idx + 1, capacity_inv, capacity);
276
while (_hashes[next_hash_idx] != EMPTY_HASH && _get_probe_length(next_hash_idx, _hashes[next_hash_idx], capacity, capacity_inv) != 0) {
277
uint32_t cur_key_idx = _hash_idx_to_key_idx[hash_idx];
278
uint32_t next_key_idx = _hash_idx_to_key_idx[next_hash_idx];
279
SWAP(_key_idx_to_hash_idx[cur_key_idx], _key_idx_to_hash_idx[next_key_idx]);
280
SWAP(_hashes[next_hash_idx], _hashes[hash_idx]);
281
SWAP(_hash_idx_to_key_idx[next_hash_idx], _hash_idx_to_key_idx[hash_idx]);
282
283
hash_idx = next_hash_idx;
284
_increment_mod(next_hash_idx, capacity);
285
}
286
287
_hashes[hash_idx] = EMPTY_HASH;
288
_keys[key_idx].~TKey();
289
_size--;
290
if (key_idx < _size) {
291
// Not the last key, move the last one here to keep keys contiguous.
292
memnew_placement(&_keys[key_idx], TKey(_keys[_size]));
293
_keys[_size].~TKey();
294
_key_idx_to_hash_idx[key_idx] = _key_idx_to_hash_idx[_size];
295
_hash_idx_to_key_idx[_key_idx_to_hash_idx[_size]] = key_idx;
296
}
297
298
return true;
299
}
300
301
// Reserves space for a number of elements, useful to avoid many resizes and rehashes.
302
// If adding a known (possibly large) number of elements at once, must be larger than old capacity.
303
void reserve(uint32_t p_new_capacity) {
304
uint32_t new_capacity_idx = _capacity_idx;
305
306
while (hash_table_size_primes[new_capacity_idx] < p_new_capacity) {
307
ERR_FAIL_COND_MSG(new_capacity_idx + 1 == (uint32_t)HASH_TABLE_SIZE_MAX, nullptr);
308
new_capacity_idx++;
309
}
310
311
if (new_capacity_idx == _capacity_idx) {
312
if (p_new_capacity < _size) {
313
WARN_VERBOSE("reserve() called with a capacity smaller than the current size. This is likely a mistake.");
314
}
315
return;
316
}
317
318
if (_keys == nullptr) {
319
_capacity_idx = new_capacity_idx;
320
return; // Unallocated yet.
321
}
322
_resize_and_rehash(new_capacity_idx);
323
}
324
325
/** Iterator API **/
326
327
struct Iterator {
328
_FORCE_INLINE_ const TKey &operator*() const {
329
return _keys[_key_idx];
330
}
331
_FORCE_INLINE_ const TKey *operator->() const {
332
return &_keys[_key_idx];
333
}
334
_FORCE_INLINE_ Iterator &operator++() {
335
_key_idx++;
336
if (_key_idx >= (int32_t)_num_keys) {
337
_key_idx = -1;
338
_keys = nullptr;
339
_num_keys = 0;
340
}
341
return *this;
342
}
343
_FORCE_INLINE_ Iterator &operator--() {
344
_key_idx--;
345
if (_key_idx < 0) {
346
_key_idx = -1;
347
_keys = nullptr;
348
_num_keys = 0;
349
}
350
return *this;
351
}
352
353
_FORCE_INLINE_ bool operator==(const Iterator &b) const { return _keys == b._keys && _key_idx == b._key_idx; }
354
_FORCE_INLINE_ bool operator!=(const Iterator &b) const { return _keys != b._keys || _key_idx != b._key_idx; }
355
356
_FORCE_INLINE_ explicit operator bool() const {
357
return _keys != nullptr;
358
}
359
360
_FORCE_INLINE_ Iterator(const TKey *p_keys, uint32_t p_num_keys, int32_t p_key_idx = -1) {
361
_keys = p_keys;
362
_num_keys = p_num_keys;
363
_key_idx = p_key_idx;
364
}
365
_FORCE_INLINE_ Iterator() {}
366
_FORCE_INLINE_ Iterator(const Iterator &p_it) {
367
_keys = p_it._keys;
368
_num_keys = p_it._num_keys;
369
_key_idx = p_it._key_idx;
370
}
371
_FORCE_INLINE_ void operator=(const Iterator &p_it) {
372
_keys = p_it._keys;
373
_num_keys = p_it._num_keys;
374
_key_idx = p_it._key_idx;
375
}
376
377
private:
378
const TKey *_keys = nullptr;
379
uint32_t _num_keys = 0;
380
int32_t _key_idx = -1;
381
};
382
383
_FORCE_INLINE_ Iterator begin() const {
384
return _size ? Iterator(_keys, _size, 0) : Iterator();
385
}
386
_FORCE_INLINE_ Iterator end() const {
387
return Iterator();
388
}
389
_FORCE_INLINE_ Iterator last() const {
390
if (_size == 0) {
391
return Iterator();
392
}
393
return Iterator(_keys, _size, _size - 1);
394
}
395
396
_FORCE_INLINE_ Iterator find(const TKey &p_key) const {
397
uint32_t key_idx = 0;
398
bool exists = _lookup_key_idx(p_key, key_idx);
399
if (!exists) {
400
return end();
401
}
402
return Iterator(_keys, _size, key_idx);
403
}
404
405
_FORCE_INLINE_ void remove(const Iterator &p_iter) {
406
if (p_iter) {
407
erase(*p_iter);
408
}
409
}
410
411
/* Insert */
412
413
Iterator insert(const TKey &p_key) {
414
uint32_t key_idx = _insert(p_key);
415
return Iterator(_keys, _size, key_idx);
416
}
417
418
/* Constructors */
419
420
HashSet(const HashSet &p_other) {
421
_init_from(p_other);
422
}
423
424
void operator=(const HashSet &p_other) {
425
if (this == &p_other) {
426
return; // Ignore self assignment.
427
}
428
429
clear();
430
431
if (_keys != nullptr) {
432
Memory::free_static(_keys);
433
Memory::free_static(_key_idx_to_hash_idx);
434
Memory::free_static(_hash_idx_to_key_idx);
435
Memory::free_static(_hashes);
436
_keys = nullptr;
437
_hashes = nullptr;
438
_hash_idx_to_key_idx = nullptr;
439
_key_idx_to_hash_idx = nullptr;
440
}
441
442
_init_from(p_other);
443
}
444
445
bool operator==(const HashSet &p_other) const {
446
if (_size != p_other._size) {
447
return false;
448
}
449
for (uint32_t i = 0; i < _size; i++) {
450
if (!p_other.has(_keys[i])) {
451
return false;
452
}
453
}
454
return true;
455
}
456
bool operator!=(const HashSet &p_other) const {
457
return !(*this == p_other);
458
}
459
460
HashSet(uint32_t p_initial_capacity) {
461
// Capacity can't be 0.
462
_capacity_idx = 0;
463
reserve(p_initial_capacity);
464
}
465
HashSet() {
466
_capacity_idx = MIN_CAPACITY_INDEX;
467
}
468
469
HashSet(std::initializer_list<TKey> p_init) {
470
reserve(p_init.size());
471
for (const TKey &E : p_init) {
472
insert(E);
473
}
474
}
475
476
void reset() {
477
clear();
478
479
if (_keys != nullptr) {
480
Memory::free_static(_keys);
481
Memory::free_static(_key_idx_to_hash_idx);
482
Memory::free_static(_hash_idx_to_key_idx);
483
Memory::free_static(_hashes);
484
_keys = nullptr;
485
_hashes = nullptr;
486
_hash_idx_to_key_idx = nullptr;
487
_key_idx_to_hash_idx = nullptr;
488
}
489
_capacity_idx = MIN_CAPACITY_INDEX;
490
}
491
492
~HashSet() {
493
clear();
494
495
if (_keys != nullptr) {
496
Memory::free_static(_keys);
497
Memory::free_static(_key_idx_to_hash_idx);
498
Memory::free_static(_hash_idx_to_key_idx);
499
Memory::free_static(_hashes);
500
}
501
}
502
};
503
504