Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/core/templates/hash_set.h
9973 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/templates/hashfuncs.h"
35
36
/**
37
* Implementation of Set using a bidi indexed hash map.
38
* Use RBSet instead of this only if the following conditions are met:
39
*
40
* - You need to keep an iterator or const pointer to Key and you intend to add/remove elements in the meantime.
41
* - Iteration order does matter (via operator<)
42
*
43
*/
44
45
template <typename TKey,
46
typename Hasher = HashMapHasherDefault,
47
typename Comparator = HashMapComparatorDefault<TKey>>
48
class HashSet {
49
public:
50
static constexpr uint32_t MIN_CAPACITY_INDEX = 2; // Use a prime.
51
static constexpr float MAX_OCCUPANCY = 0.75;
52
static constexpr uint32_t EMPTY_HASH = 0;
53
54
private:
55
TKey *keys = nullptr;
56
uint32_t *hash_to_key = nullptr;
57
uint32_t *key_to_hash = nullptr;
58
uint32_t *hashes = nullptr;
59
60
uint32_t capacity_index = 0;
61
uint32_t num_elements = 0;
62
63
_FORCE_INLINE_ uint32_t _hash(const TKey &p_key) const {
64
uint32_t hash = Hasher::hash(p_key);
65
66
if (unlikely(hash == EMPTY_HASH)) {
67
hash = EMPTY_HASH + 1;
68
}
69
70
return hash;
71
}
72
73
_FORCE_INLINE_ static constexpr void _increment_mod(uint32_t &r_pos, const uint32_t p_capacity) {
74
r_pos++;
75
// `if` is faster than both fastmod and mod.
76
if (unlikely(r_pos == p_capacity)) {
77
r_pos = 0;
78
}
79
}
80
81
static _FORCE_INLINE_ uint32_t _get_probe_length(const uint32_t p_pos, const uint32_t p_hash, const uint32_t p_capacity, const uint64_t p_capacity_inv) {
82
const uint32_t original_pos = fastmod(p_hash, p_capacity_inv, p_capacity);
83
const uint32_t distance_pos = p_pos - original_pos + p_capacity;
84
// At most p_capacity over 0, so we can use an if (faster than fastmod).
85
return distance_pos >= p_capacity ? distance_pos - p_capacity : distance_pos;
86
}
87
88
bool _lookup_pos(const TKey &p_key, uint32_t &r_pos) const {
89
if (keys == nullptr || num_elements == 0) {
90
return false; // Failed lookups, no elements
91
}
92
93
const uint32_t capacity = hash_table_size_primes[capacity_index];
94
const uint64_t capacity_inv = hash_table_size_primes_inv[capacity_index];
95
uint32_t hash = _hash(p_key);
96
uint32_t pos = fastmod(hash, capacity_inv, capacity);
97
uint32_t distance = 0;
98
99
while (true) {
100
if (hashes[pos] == EMPTY_HASH) {
101
return false;
102
}
103
104
if (hashes[pos] == hash && Comparator::compare(keys[hash_to_key[pos]], p_key)) {
105
r_pos = hash_to_key[pos];
106
return true;
107
}
108
109
if (distance > _get_probe_length(pos, hashes[pos], capacity, capacity_inv)) {
110
return false;
111
}
112
113
_increment_mod(pos, capacity);
114
distance++;
115
}
116
}
117
118
uint32_t _insert_with_hash(uint32_t p_hash, uint32_t p_index) {
119
const uint32_t capacity = hash_table_size_primes[capacity_index];
120
const uint64_t capacity_inv = hash_table_size_primes_inv[capacity_index];
121
uint32_t hash = p_hash;
122
uint32_t index = p_index;
123
uint32_t distance = 0;
124
uint32_t pos = fastmod(hash, capacity_inv, capacity);
125
126
while (true) {
127
if (hashes[pos] == EMPTY_HASH) {
128
hashes[pos] = hash;
129
key_to_hash[index] = pos;
130
hash_to_key[pos] = index;
131
return pos;
132
}
133
134
// Not an empty slot, let's check the probing length of the existing one.
135
uint32_t existing_probe_len = _get_probe_length(pos, hashes[pos], capacity, capacity_inv);
136
if (existing_probe_len < distance) {
137
key_to_hash[index] = pos;
138
SWAP(hash, hashes[pos]);
139
SWAP(index, hash_to_key[pos]);
140
distance = existing_probe_len;
141
}
142
143
_increment_mod(pos, capacity);
144
distance++;
145
}
146
}
147
148
void _resize_and_rehash(uint32_t p_new_capacity_index) {
149
// Capacity can't be 0.
150
capacity_index = MAX((uint32_t)MIN_CAPACITY_INDEX, p_new_capacity_index);
151
152
uint32_t capacity = hash_table_size_primes[capacity_index];
153
154
uint32_t *old_hashes = hashes;
155
uint32_t *old_key_to_hash = key_to_hash;
156
157
static_assert(EMPTY_HASH == 0, "Assuming EMPTY_HASH = 0 for alloc_static_zeroed call");
158
hashes = reinterpret_cast<uint32_t *>(Memory::alloc_static_zeroed(sizeof(uint32_t) * capacity));
159
keys = reinterpret_cast<TKey *>(Memory::realloc_static(keys, sizeof(TKey) * capacity));
160
key_to_hash = reinterpret_cast<uint32_t *>(Memory::alloc_static(sizeof(uint32_t) * capacity));
161
hash_to_key = reinterpret_cast<uint32_t *>(Memory::realloc_static(hash_to_key, sizeof(uint32_t) * capacity));
162
163
for (uint32_t i = 0; i < num_elements; i++) {
164
uint32_t h = old_hashes[old_key_to_hash[i]];
165
_insert_with_hash(h, i);
166
}
167
168
Memory::free_static(old_hashes);
169
Memory::free_static(old_key_to_hash);
170
}
171
172
_FORCE_INLINE_ int32_t _insert(const TKey &p_key) {
173
uint32_t capacity = hash_table_size_primes[capacity_index];
174
if (unlikely(keys == nullptr)) {
175
// Allocate on demand to save memory.
176
177
static_assert(EMPTY_HASH == 0, "Assuming EMPTY_HASH = 0 for alloc_static_zeroed call");
178
hashes = reinterpret_cast<uint32_t *>(Memory::alloc_static_zeroed(sizeof(uint32_t) * capacity));
179
keys = reinterpret_cast<TKey *>(Memory::alloc_static(sizeof(TKey) * capacity));
180
key_to_hash = reinterpret_cast<uint32_t *>(Memory::alloc_static(sizeof(uint32_t) * capacity));
181
hash_to_key = reinterpret_cast<uint32_t *>(Memory::alloc_static(sizeof(uint32_t) * capacity));
182
}
183
184
uint32_t pos = 0;
185
bool exists = _lookup_pos(p_key, pos);
186
187
if (exists) {
188
return pos;
189
} else {
190
if (num_elements + 1 > MAX_OCCUPANCY * capacity) {
191
ERR_FAIL_COND_V_MSG(capacity_index + 1 == HASH_TABLE_SIZE_MAX, -1, "Hash table maximum capacity reached, aborting insertion.");
192
_resize_and_rehash(capacity_index + 1);
193
}
194
195
uint32_t hash = _hash(p_key);
196
memnew_placement(&keys[num_elements], TKey(p_key));
197
_insert_with_hash(hash, num_elements);
198
num_elements++;
199
return num_elements - 1;
200
}
201
}
202
203
void _init_from(const HashSet &p_other) {
204
capacity_index = p_other.capacity_index;
205
num_elements = p_other.num_elements;
206
207
if (p_other.num_elements == 0) {
208
return;
209
}
210
211
uint32_t capacity = hash_table_size_primes[capacity_index];
212
213
hashes = reinterpret_cast<uint32_t *>(Memory::alloc_static(sizeof(uint32_t) * capacity));
214
keys = reinterpret_cast<TKey *>(Memory::alloc_static(sizeof(TKey) * capacity));
215
key_to_hash = reinterpret_cast<uint32_t *>(Memory::alloc_static(sizeof(uint32_t) * capacity));
216
hash_to_key = reinterpret_cast<uint32_t *>(Memory::alloc_static(sizeof(uint32_t) * capacity));
217
218
for (uint32_t i = 0; i < num_elements; i++) {
219
memnew_placement(&keys[i], TKey(p_other.keys[i]));
220
key_to_hash[i] = p_other.key_to_hash[i];
221
}
222
223
for (uint32_t i = 0; i < capacity; i++) {
224
hashes[i] = p_other.hashes[i];
225
hash_to_key[i] = p_other.hash_to_key[i];
226
}
227
}
228
229
public:
230
_FORCE_INLINE_ uint32_t get_capacity() const { return hash_table_size_primes[capacity_index]; }
231
_FORCE_INLINE_ uint32_t size() const { return num_elements; }
232
233
/* Standard Godot Container API */
234
235
bool is_empty() const {
236
return num_elements == 0;
237
}
238
239
void clear() {
240
if (keys == nullptr || num_elements == 0) {
241
return;
242
}
243
uint32_t capacity = hash_table_size_primes[capacity_index];
244
for (uint32_t i = 0; i < capacity; i++) {
245
hashes[i] = EMPTY_HASH;
246
}
247
for (uint32_t i = 0; i < num_elements; i++) {
248
keys[i].~TKey();
249
}
250
251
num_elements = 0;
252
}
253
254
_FORCE_INLINE_ bool has(const TKey &p_key) const {
255
uint32_t _pos = 0;
256
return _lookup_pos(p_key, _pos);
257
}
258
259
bool erase(const TKey &p_key) {
260
uint32_t pos = 0;
261
bool exists = _lookup_pos(p_key, pos);
262
263
if (!exists) {
264
return false;
265
}
266
267
uint32_t key_pos = pos;
268
pos = key_to_hash[pos]; //make hash pos
269
270
const uint32_t capacity = hash_table_size_primes[capacity_index];
271
const uint64_t capacity_inv = hash_table_size_primes_inv[capacity_index];
272
uint32_t next_pos = fastmod(pos + 1, capacity_inv, capacity);
273
while (hashes[next_pos] != EMPTY_HASH && _get_probe_length(next_pos, hashes[next_pos], capacity, capacity_inv) != 0) {
274
uint32_t kpos = hash_to_key[pos];
275
uint32_t kpos_next = hash_to_key[next_pos];
276
SWAP(key_to_hash[kpos], key_to_hash[kpos_next]);
277
SWAP(hashes[next_pos], hashes[pos]);
278
SWAP(hash_to_key[next_pos], hash_to_key[pos]);
279
280
pos = next_pos;
281
_increment_mod(next_pos, capacity);
282
}
283
284
hashes[pos] = EMPTY_HASH;
285
keys[key_pos].~TKey();
286
num_elements--;
287
if (key_pos < num_elements) {
288
// Not the last key, move the last one here to keep keys lineal
289
memnew_placement(&keys[key_pos], TKey(keys[num_elements]));
290
keys[num_elements].~TKey();
291
key_to_hash[key_pos] = key_to_hash[num_elements];
292
hash_to_key[key_to_hash[num_elements]] = key_pos;
293
}
294
295
return true;
296
}
297
298
// Reserves space for a number of elements, useful to avoid many resizes and rehashes.
299
// If adding a known (possibly large) number of elements at once, must be larger than old capacity.
300
void reserve(uint32_t p_new_capacity) {
301
ERR_FAIL_COND_MSG(p_new_capacity < size(), "reserve() called with a capacity smaller than the current size. This is likely a mistake.");
302
uint32_t new_index = capacity_index;
303
304
while (hash_table_size_primes[new_index] < p_new_capacity) {
305
ERR_FAIL_COND_MSG(new_index + 1 == (uint32_t)HASH_TABLE_SIZE_MAX, nullptr);
306
new_index++;
307
}
308
309
if (new_index == capacity_index) {
310
return;
311
}
312
313
if (keys == nullptr) {
314
capacity_index = new_index;
315
return; // Unallocated yet.
316
}
317
_resize_and_rehash(new_index);
318
}
319
320
/** Iterator API **/
321
322
struct Iterator {
323
_FORCE_INLINE_ const TKey &operator*() const {
324
return keys[index];
325
}
326
_FORCE_INLINE_ const TKey *operator->() const {
327
return &keys[index];
328
}
329
_FORCE_INLINE_ Iterator &operator++() {
330
index++;
331
if (index >= (int32_t)num_keys) {
332
index = -1;
333
keys = nullptr;
334
num_keys = 0;
335
}
336
return *this;
337
}
338
_FORCE_INLINE_ Iterator &operator--() {
339
index--;
340
if (index < 0) {
341
index = -1;
342
keys = nullptr;
343
num_keys = 0;
344
}
345
return *this;
346
}
347
348
_FORCE_INLINE_ bool operator==(const Iterator &b) const { return keys == b.keys && index == b.index; }
349
_FORCE_INLINE_ bool operator!=(const Iterator &b) const { return keys != b.keys || index != b.index; }
350
351
_FORCE_INLINE_ explicit operator bool() const {
352
return keys != nullptr;
353
}
354
355
_FORCE_INLINE_ Iterator(const TKey *p_keys, uint32_t p_num_keys, int32_t p_index = -1) {
356
keys = p_keys;
357
num_keys = p_num_keys;
358
index = p_index;
359
}
360
_FORCE_INLINE_ Iterator() {}
361
_FORCE_INLINE_ Iterator(const Iterator &p_it) {
362
keys = p_it.keys;
363
num_keys = p_it.num_keys;
364
index = p_it.index;
365
}
366
_FORCE_INLINE_ void operator=(const Iterator &p_it) {
367
keys = p_it.keys;
368
num_keys = p_it.num_keys;
369
index = p_it.index;
370
}
371
372
private:
373
const TKey *keys = nullptr;
374
uint32_t num_keys = 0;
375
int32_t index = -1;
376
};
377
378
_FORCE_INLINE_ Iterator begin() const {
379
return num_elements ? Iterator(keys, num_elements, 0) : Iterator();
380
}
381
_FORCE_INLINE_ Iterator end() const {
382
return Iterator();
383
}
384
_FORCE_INLINE_ Iterator last() const {
385
if (num_elements == 0) {
386
return Iterator();
387
}
388
return Iterator(keys, num_elements, num_elements - 1);
389
}
390
391
_FORCE_INLINE_ Iterator find(const TKey &p_key) const {
392
uint32_t pos = 0;
393
bool exists = _lookup_pos(p_key, pos);
394
if (!exists) {
395
return end();
396
}
397
return Iterator(keys, num_elements, pos);
398
}
399
400
_FORCE_INLINE_ void remove(const Iterator &p_iter) {
401
if (p_iter) {
402
erase(*p_iter);
403
}
404
}
405
406
/* Insert */
407
408
Iterator insert(const TKey &p_key) {
409
uint32_t pos = _insert(p_key);
410
return Iterator(keys, num_elements, pos);
411
}
412
413
/* Constructors */
414
415
HashSet(const HashSet &p_other) {
416
_init_from(p_other);
417
}
418
419
void operator=(const HashSet &p_other) {
420
if (this == &p_other) {
421
return; // Ignore self assignment.
422
}
423
424
clear();
425
426
if (keys != nullptr) {
427
Memory::free_static(keys);
428
Memory::free_static(key_to_hash);
429
Memory::free_static(hash_to_key);
430
Memory::free_static(hashes);
431
keys = nullptr;
432
hashes = nullptr;
433
hash_to_key = nullptr;
434
key_to_hash = nullptr;
435
}
436
437
_init_from(p_other);
438
}
439
440
bool operator==(const HashSet &p_other) const {
441
if (num_elements != p_other.num_elements) {
442
return false;
443
}
444
for (uint32_t i = 0; i < num_elements; i++) {
445
if (!p_other.has(keys[i])) {
446
return false;
447
}
448
}
449
return true;
450
}
451
bool operator!=(const HashSet &p_other) const {
452
return !(*this == p_other);
453
}
454
455
HashSet(uint32_t p_initial_capacity) {
456
// Capacity can't be 0.
457
capacity_index = 0;
458
reserve(p_initial_capacity);
459
}
460
HashSet() {
461
capacity_index = MIN_CAPACITY_INDEX;
462
}
463
464
HashSet(std::initializer_list<TKey> p_init) {
465
reserve(p_init.size());
466
for (const TKey &E : p_init) {
467
insert(E);
468
}
469
}
470
471
void reset() {
472
clear();
473
474
if (keys != nullptr) {
475
Memory::free_static(keys);
476
Memory::free_static(key_to_hash);
477
Memory::free_static(hash_to_key);
478
Memory::free_static(hashes);
479
keys = nullptr;
480
hashes = nullptr;
481
hash_to_key = nullptr;
482
key_to_hash = nullptr;
483
}
484
capacity_index = MIN_CAPACITY_INDEX;
485
}
486
487
~HashSet() {
488
clear();
489
490
if (keys != nullptr) {
491
Memory::free_static(keys);
492
Memory::free_static(key_to_hash);
493
Memory::free_static(hash_to_key);
494
Memory::free_static(hashes);
495
}
496
}
497
};
498
499