FastLED 3.9.15
Loading...
Searching...
No Matches
hash_map.h
Go to the documentation of this file.
1#pragma once
2
3/*
4HashMap that is optimized for embedded devices. The hashmap
5will store upto N elements inline, and will spill over to a heap.
6This hashmap will try not to grow by detecting during rehash that
7the number of tombstones is greater than the number of elements.
8This will keep the memory from growing during multiple inserts
9and removals.
10*/
11
12// #include <cstddef>
13// #include <iterator>
14
15#include "fl/assert.h"
16#include "fl/bitset.h"
17#include "fl/clamp.h"
18#include "fl/hash.h"
19#include "fl/map_range.h"
20#include "fl/optional.h"
21#include "fl/pair.h"
22#include "fl/template_magic.h"
23#include "fl/vector.h"
24#include "fl/warn.h"
25
26namespace fl {
27
28// // begin using declarations for stl compatibility
29// use fl::equal_to;
30// use fl::hash_map;
31// use fl::unordered_map;
32// // end using declarations for stl compatibility
33
34template <typename T> struct EqualTo {
35 bool operator()(const T &a, const T &b) const { return a == b; }
36};
37
38// -- HashMap class
39// ------------------------------------------------------------- Begin HashMap
40// class
41
42#ifndef FASTLED_HASHMAP_INLINED_COUNT
43#define FASTLED_HASHMAP_INLINED_COUNT 8
44#endif
45
46template <typename Key, typename T, typename Hash = Hash<Key>,
47 typename KeyEqual = EqualTo<Key>,
48 int INLINED_COUNT = FASTLED_HASHMAP_INLINED_COUNT>
49class HashMap {
50 public:
51 HashMap(size_t initial_capacity = FASTLED_HASHMAP_INLINED_COUNT,
52 float max_load = 0.7f)
53 : _buckets(next_power_of_two(initial_capacity)), _size(0),
54 _tombstones(0), _occupied(next_power_of_two(initial_capacity)),
55 _deleted(next_power_of_two(initial_capacity)) {
56 setLoadFactor(max_load);
57 }
58
59 void setLoadFactor(float f) {
60 f = fl::clamp(f, 0.f, 1.f);
61 mLoadFactor = fl::map_range<float, uint8_t>(f, 0.f, 1.f, 0, 255);
62 }
63
64 // Iterator support.
65 struct iterator {
66 // Standard iterator typedefs
67 // using difference_type = std::ptrdiff_t;
71 // using iterator_category = std::forward_iterator_tag;
72
73 iterator() : _map(nullptr), _idx(0) {}
74 iterator(HashMap *m, size_t idx) : _map(m), _idx(idx) {
76 }
77
79 auto &e = _map->_buckets[_idx];
80 return {e.key, e.value};
81 }
82
87
89 ++_idx;
91 return *this;
92 }
93
95 iterator tmp = *this;
96 ++(*this);
97 return tmp;
98 }
99
100 bool operator==(const iterator &o) const {
101 return _map == o._map && _idx == o._idx;
102 }
103 bool operator!=(const iterator &o) const { return !(*this == o); }
104
106 if (!_map)
107 return;
108 size_t cap = _map->_buckets.size();
110 ++_idx;
111 }
112
113 private:
115 size_t _idx;
117 };
118
120 // Standard iterator typedefs
121 // using difference_type = std::ptrdiff_t;
123 using pointer = const value_type *;
124 using reference = const value_type &;
125 // using iterator_category = std::forward_iterator_tag;
126
127 const_iterator() : _map(nullptr), _idx(0) {}
128 const_iterator(const HashMap *m, size_t idx) : _map(m), _idx(idx) {
130 }
131 const_iterator(const iterator &it) : _map(it._map), _idx(it._idx) {}
132
134 auto &e = _map->_buckets[_idx];
135 return {e.key, e.value};
136 }
137
140 return &_cached_value;
141 }
142
144 ++_idx;
146 return *this;
147 }
148
150 const_iterator tmp = *this;
151 ++(*this);
152 return tmp;
153 }
154
155 bool operator==(const const_iterator &o) const {
156 return _map == o._map && _idx == o._idx;
157 }
158 bool operator!=(const const_iterator &o) const { return !(*this == o); }
159
161 if (!_map)
162 return;
163 size_t cap = _map->_buckets.size();
165 ++_idx;
166 }
167
168 friend class HashMap;
169
170 private:
171 const HashMap *_map;
172 size_t _idx;
174 };
175
176 iterator begin() { return iterator(this, 0); }
177 iterator end() { return iterator(this, _buckets.size()); }
178 const_iterator begin() const { return const_iterator(this, 0); }
179 const_iterator end() const { return const_iterator(this, _buckets.size()); }
180 const_iterator cbegin() const { return const_iterator(this, 0); }
181 const_iterator cend() const {
182 return const_iterator(this, _buckets.size());
183 }
184
185 static bool NeedsRehash(size_t size, size_t bucket_size, size_t tombstones,
186 uint8_t load_factor) {
187 // (size + tombstones) << 8 : multiply numerator by 256
188 // capacity * max_load : denominator * threshold
189 uint32_t lhs = (size + tombstones) << 8;
190 uint32_t rhs = (bucket_size * load_factor);
191 return lhs > rhs;
192 }
193
194 // returns true if (size + tombs)/capacity > _max_load/256
195 bool needs_rehash() const {
197 }
198
199 // insert or overwrite
200 void insert(const Key &key, const T &value) {
201 const bool will_rehash = needs_rehash();
202 if (will_rehash) {
203 // if half the buckets are tombstones, rehash inline to prevent
204 // memory spill over into the heap.
205 if (_tombstones > _size) {
207 } else {
208 rehash(_buckets.size() * 2);
209 }
210 }
211 size_t idx;
212 bool is_new;
214 idx = p.first;
215 is_new = p.second;
216 if (is_new) {
217 _buckets[idx].key = key;
218 _buckets[idx].value = value;
219 mark_occupied(idx);
220 ++_size;
221 } else {
222 FASTLED_ASSERT(idx != npos, "HashMap::insert: invalid index at "
223 << idx << " which is " << npos);
224 _buckets[idx].value = value;
225 }
226 }
227
228 // remove key; returns true if removed
229 bool remove(const Key &key) {
230 auto idx = find_index(key);
231 if (idx == npos)
232 return false;
233 mark_deleted(idx);
234 --_size;
235 ++_tombstones;
236 return true;
237 }
238
239 bool erase(const Key &key) { return remove(key); }
240
241 void clear() {
242 _buckets.assign(_buckets.size(), Entry{});
243 _occupied.reset();
244 _deleted.reset();
245 _size = _tombstones = 0;
246 }
247
248 // find pointer to value or nullptr
249 T *find_value(const Key &key) {
250 auto idx = find_index(key);
251 return idx == npos ? nullptr : &_buckets[idx].value;
252 }
253
254 const T *find_value(const Key &key) const {
255 auto idx = find_index(key);
256 return idx == npos ? nullptr : &_buckets[idx].value;
257 }
258
259 iterator find(const Key &key) {
260 auto idx = find_index(key);
261 return idx == npos ? end() : iterator(this, idx);
262 }
263
264 const_iterator find(const Key &key) const {
265 auto idx = find_index(key);
266 return idx == npos ? end() : const_iterator(this, idx);
267 }
268
269 // access or default-construct
270 T &operator[](const Key &key) {
271 size_t idx;
272 bool is_new;
273
275 idx = p.first;
276 is_new = p.second;
277 if (is_new) {
278 _buckets[idx].key = key;
279 _buckets[idx].value = T{};
280 mark_occupied(idx);
281 ++_size;
282 }
283 return _buckets[idx].value;
284 }
285
286 size_t size() const { return _size; }
287 bool empty() const { return _size == 0; }
288 size_t capacity() const { return _buckets.size(); }
289
290 private:
291 static constexpr size_t npos = size_t(-1);
292
293 // Helper methods to check entry state
294 bool is_occupied(size_t idx) const { return _occupied.test(idx); }
295
296 bool is_deleted(size_t idx) const { return _deleted.test(idx); }
297
298 bool is_empty(size_t idx) const {
299 return !is_occupied(idx) && !is_deleted(idx);
300 }
301
302 void mark_occupied(size_t idx) {
303 _occupied.set(idx);
304 _deleted.reset(idx);
305 }
306
307 void mark_deleted(size_t idx) {
308 _occupied.reset(idx);
309 _deleted.set(idx);
310 }
311
312 void mark_empty(size_t idx) {
313 _occupied.reset(idx);
314 _deleted.reset(idx);
315 }
316
317 struct Entry {
320 void swap(Entry &other) {
321 fl::swap(key, other.key);
322 fl::swap(value, other.value);
323 }
324 };
325
326 static size_t next_power_of_two(size_t n) {
327 size_t p = 1;
328 while (p < n)
329 p <<= 1;
330 return p;
331 }
332
333 pair<size_t, bool> find_slot(const Key &key) const {
334 const size_t cap = _buckets.size();
335 const size_t mask = cap - 1;
336 const size_t h = _hash(key) & mask;
337 size_t first_tomb = npos;
338
339 if (cap <= 8) {
340 // linear probing
341 for (size_t i = 0; i < cap; ++i) {
342 const size_t idx = (h + i) & mask;
343
344 if (is_empty(idx))
345 return {first_tomb != npos ? first_tomb : idx, true};
346 if (is_deleted(idx)) {
347 if (first_tomb == npos)
348 first_tomb = idx;
349 } else if (is_occupied(idx) && _equal(_buckets[idx].key, key)) {
350 return {idx, false};
351 }
352 }
353 } else {
354 // quadratic probing up to 8 tries
355 size_t i = 0;
356 for (; i < 8; ++i) {
357 const size_t idx = (h + i + i * i) & mask;
358
359 if (is_empty(idx))
360 return {first_tomb != npos ? first_tomb : idx, true};
361 if (is_deleted(idx)) {
362 if (first_tomb == npos)
363 first_tomb = idx;
364 } else if (is_occupied(idx) && _equal(_buckets[idx].key, key)) {
365 return {idx, false};
366 }
367 }
368 // fallback to linear for the rest
369 for (; i < cap; ++i) {
370 const size_t idx = (h + i) & mask;
371
372 if (is_empty(idx))
373 return {first_tomb != npos ? first_tomb : idx, true};
374 if (is_deleted(idx)) {
375 if (first_tomb == npos)
376 first_tomb = idx;
377 } else if (is_occupied(idx) && _equal(_buckets[idx].key, key)) {
378 return {idx, false};
379 }
380 }
381 }
382
383 return {npos, false};
384 }
385
386 enum {
389 };
390
391 size_t find_index(const Key &key) const {
392 const size_t cap = _buckets.size();
393 const size_t mask = cap - 1;
394 const size_t h = _hash(key) & mask;
395
396 if (cap <= kLinearProbingOnlySize) {
397 // linear probing
398 for (size_t i = 0; i < cap; ++i) {
399 const size_t idx = (h + i) & mask;
400 if (is_empty(idx))
401 return npos;
402 if (is_occupied(idx) && _equal(_buckets[idx].key, key))
403 return idx;
404 }
405 } else {
406 // quadratic probing up to 8 tries
407 size_t i = 0;
408 for (; i < kQuadraticProbingTries; ++i) {
409 const size_t idx = (h + i + i * i) & mask;
410 if (is_empty(idx))
411 return npos;
412 if (is_occupied(idx) && _equal(_buckets[idx].key, key))
413 return idx;
414 }
415 // fallback to linear for the rest
416 for (; i < cap; ++i) {
417 const size_t idx = (h + i) & mask;
418 if (is_empty(idx))
419 return npos;
420 if (is_occupied(idx) && _equal(_buckets[idx].key, key))
421 return idx;
422 }
423 }
424
425 return npos;
426 }
427
429 const Key &key, const fl::bitset<1024> &occupied_set) const {
430 const size_t cap = _buckets.size();
431 const size_t mask = cap - 1;
432 const size_t h = _hash(key) & mask;
433
434 if (cap <= kLinearProbingOnlySize) {
435 // linear probing
436 for (size_t i = 0; i < cap; ++i) {
437 const size_t idx = (h + i) & mask;
438 bool occupied = occupied_set.test(idx);
439 if (occupied) {
440 continue;
441 }
442 return idx;
443 }
444 } else {
445 // quadratic probing up to 8 tries
446 size_t i = 0;
447 for (; i < kQuadraticProbingTries; ++i) {
448 const size_t idx = (h + i + i * i) & mask;
449 bool occupied = occupied_set.test(idx);
450 if (occupied) {
451 continue;
452 }
453 return idx;
454 }
455 // fallback to linear for the rest
456 for (; i < cap; ++i) {
457 const size_t idx = (h + i) & mask;
458 bool occupied = occupied_set.test(idx);
459 if (occupied) {
460 continue;
461 }
462 return idx;
463 }
464 }
465 return npos;
466 }
467
468 void rehash(size_t new_cap) {
469 new_cap = next_power_of_two(new_cap);
471 fl::bitset<1024> old_occupied = _occupied;
472
473 _buckets.swap(old);
474 _buckets.clear();
475 _buckets.assign(new_cap, Entry{});
476
477 _occupied.reset();
478 _occupied.resize(new_cap);
479 _deleted.reset();
480 _deleted.resize(new_cap);
481
482 _size = _tombstones = 0;
483
484 for (size_t i = 0; i < old.size(); i++) {
485 if (old_occupied.test(i))
486 insert(old[i].key, old[i].value);
487 }
488 }
489
491 // filter out tombstones and compact
492 size_t cap = _buckets.size();
493 size_t pos = 0;
494 // compact live elements to the left.
495 for (size_t i = 0; i < cap; ++i) {
496 if (is_occupied(i)) {
497 if (pos != i) {
498 _buckets[pos] = _buckets[i];
499 mark_empty(i);
501 }
502 ++pos;
503 } else if (is_deleted(i)) {
504 mark_empty(i);
505 }
506 }
507
508 fl::bitset<1024> occupied; // Preallocate a bitset of size 1024
509 // swap the components, this will happen at most N times,
510 // use the occupied bitset to track which entries are occupied
511 // in the array rather than just copied in.
513 for (size_t i = 0; i < _size; ++i) {
514 const bool already_finished = occupied.test(i);
515 if (already_finished) {
516 continue;
517 }
518 auto &e = _buckets[i];
519 // FASTLED_ASSERT(e.state == EntryState::Occupied,
520 // "HashMap::rehash_inline_no_resize: invalid
521 // state");
522
523 size_t idx = find_unoccupied_index_using_bitset(e.key, occupied);
524 if (idx == npos) {
525 // no more space
526 FASTLED_ASSERT(
527 false, "HashMap::rehash_inline_no_resize: invalid index at "
528 << idx << " which is " << npos);
529 return;
530 }
531 // if idx < pos then we are moving the entry to a new location
532 FASTLED_ASSERT(!tmp,
533 "HashMap::rehash_inline_no_resize: invalid tmp");
534 if (idx >= _size) {
535 // directly copy it now
536 _buckets[idx] = e;
537 continue;
538 }
539 tmp = e;
540 occupied.set(idx);
541 _buckets[idx] = *tmp.ptr();
542 while (!tmp.empty()) {
543 // we have to find a place for temp.
544 // find new position for tmp.
545 auto key = tmp.ptr()->key;
546 size_t new_idx =
548 if (new_idx == npos) {
549 // no more space
550 FASTLED_ASSERT(
551 false,
552 "HashMap::rehash_inline_no_resize: invalid index at "
553 << new_idx << " which is " << npos);
554 return;
555 }
556 occupied.set(new_idx);
557 if (new_idx < _size) {
558 // we have to swap the entry at new_idx with tmp
559 optional<Entry> tmp2 = _buckets[new_idx];
560 _buckets[new_idx] = *tmp.ptr();
561 tmp = tmp2;
562 } else {
563 // we can just move tmp to new_idx
564 _buckets[new_idx] = *tmp.ptr();
565 tmp.reset();
566 }
567 }
568 FASTLED_ASSERT(
569 occupied.test(i),
570 "HashMap::rehash_inline_no_resize: invalid occupied at " << i);
571 FASTLED_ASSERT(
572 !tmp, "HashMap::rehash_inline_no_resize: invalid tmp at " << i);
573 }
574 }
575
577 size_t _size;
579 uint8_t mLoadFactor;
583 KeyEqual _equal;
584};
585
586// begin using declarations for stl compatibility
587template <typename T> using equal_to = EqualTo<T>;
588
589template <typename Key, typename T, typename Hash = Hash<Key>,
590 typename KeyEqual = equal_to<Key>>
592
593template <typename Key, typename T, typename Hash = Hash<Key>,
594 typename KeyEqual = equal_to<Key>>
596// end using declarations for stl compatibility
597
598} // namespace fl
uint8_t pos
Definition Blur.ino:11
bool test(uint32_t pos) const noexcept
Tests whether the bit at position pos is set.
Definition bitset.h:383
BitsetInlined & set(uint32_t pos, bool value=true)
Sets or clears the bit at position pos.
Definition bitset.h:332
void mark_occupied(size_t idx)
Definition hash_map.h:302
static size_t next_power_of_two(size_t n)
Definition hash_map.h:326
void mark_deleted(size_t idx)
Definition hash_map.h:307
iterator end()
Definition hash_map.h:177
static constexpr size_t npos
Definition hash_map.h:291
void mark_empty(size_t idx)
Definition hash_map.h:312
T & operator[](const Key &key)
Definition hash_map.h:270
bool erase(const Key &key)
Definition hash_map.h:239
const T * find_value(const Key &key) const
Definition hash_map.h:254
void rehash_inline_no_resize()
Definition hash_map.h:490
bool is_empty(size_t idx) const
Definition hash_map.h:298
const_iterator cend() const
Definition hash_map.h:181
iterator begin()
Definition hash_map.h:176
HashMap(size_t initial_capacity=FASTLED_HASHMAP_INLINED_COUNT, float max_load=0.7f)
Definition hash_map.h:51
size_t find_index(const Key &key) const
Definition hash_map.h:391
KeyEqual _equal
Definition hash_map.h:583
void clear()
Definition hash_map.h:241
T * find_value(const Key &key)
Definition hash_map.h:249
size_t capacity() const
Definition hash_map.h:288
size_t find_unoccupied_index_using_bitset(const Key &key, const fl::bitset< 1024 > &occupied_set) const
Definition hash_map.h:428
bool remove(const Key &key)
Definition hash_map.h:229
void rehash(size_t new_cap)
Definition hash_map.h:468
const_iterator end() const
Definition hash_map.h:179
bool is_occupied(size_t idx) const
Definition hash_map.h:294
static bool NeedsRehash(size_t size, size_t bucket_size, size_t tombstones, uint8_t load_factor)
Definition hash_map.h:185
bool empty() const
Definition hash_map.h:287
const_iterator find(const Key &key) const
Definition hash_map.h:264
pair< size_t, bool > find_slot(const Key &key) const
Definition hash_map.h:333
fl::vector_inlined< Entry, FASTLED_HASHMAP_INLINED_COUNT > _buckets
Definition hash_map.h:576
void setLoadFactor(float f)
Definition hash_map.h:59
const_iterator begin() const
Definition hash_map.h:178
const_iterator cbegin() const
Definition hash_map.h:180
void insert(const Key &key, const T &value)
Definition hash_map.h:200
bool needs_rehash() const
Definition hash_map.h:195
iterator find(const Key &key)
Definition hash_map.h:259
bool is_deleted(size_t idx) const
Definition hash_map.h:296
size_t size() const
Definition vector.h:866
bool empty() const
Definition optional.h:21
void reset()
Definition optional.h:25
T * ptr()
Definition optional.h:22
#define FASTLED_HASHMAP_INLINED_COUNT
Definition hash_map.h:43
BitsetInlined< N > bitset
Definition bitset.h:15
void swap(array< T, N > &lhs, array< T, N > &rhs) noexcept(noexcept(lhs.swap(rhs)))
Definition array.h:140
InlinedVector< T, INLINED_SIZE > vector_inlined
Definition vector.h:1034
Optional< T > optional
Definition optional.h:10
FASTLED_FORCE_INLINE T clamp(T value, T min, T max)
Definition clamp.h:10
Pair< Key, Value > pair
Definition pair.h:13
HashMap< Key, T, Hash, KeyEqual > hash_map
Definition hash_map.h:591
EqualTo< T > equal_to
Definition hash_map.h:587
HashMap< Key, T, Hash, KeyEqual > unordered_map
Definition hash_map.h:595
FASTLED_FORCE_INLINE U map_range(T value, T in_min, T in_max, U out_min, U out_max)
Definition map_range.h:26
Implements a simple red square effect for 2D LED grids.
Definition crgb.h:16
static FASTLED_NAMESPACE_BEGIN uint8_t const p[]
Definition noise.cpp:30
Definition Keyboard.h:22
bool operator()(const T &a, const T &b) const
Definition hash_map.h:35
T value
Definition hash_map.h:319
void swap(Entry &other)
Definition hash_map.h:320
Key key
Definition hash_map.h:318
Definition hash_map.h:317
const_iterator operator++(int)
Definition hash_map.h:149
const value_type * pointer
Definition hash_map.h:123
const_iterator & operator++()
Definition hash_map.h:143
bool operator==(const const_iterator &o) const
Definition hash_map.h:155
value_type operator*() const
Definition hash_map.h:133
const_iterator(const iterator &it)
Definition hash_map.h:131
pair< const Key, T > value_type
Definition hash_map.h:122
bool operator!=(const const_iterator &o) const
Definition hash_map.h:158
const_iterator(const HashMap *m, size_t idx)
Definition hash_map.h:128
const value_type & reference
Definition hash_map.h:124
pointer operator->() const
Definition hash_map.h:138
value_type & reference
Definition hash_map.h:70
iterator operator++(int)
Definition hash_map.h:94
pointer operator->()
Definition hash_map.h:83
pair< const Key, T > value_type
Definition hash_map.h:68
iterator & operator++()
Definition hash_map.h:88
value_type operator*() const
Definition hash_map.h:78
value_type _cached_value
Definition hash_map.h:116
bool operator!=(const iterator &o) const
Definition hash_map.h:103
bool operator==(const iterator &o) const
Definition hash_map.h:100
iterator(HashMap *m, size_t idx)
Definition hash_map.h:74
value_type * pointer
Definition hash_map.h:69