FastLED 3.9.15
Loading...
Searching...
No Matches
unordered_map.h
Go to the documentation of this file.
1#pragma once
2
3/*
4unordered_map that is optimized for embedded devices. The unordered_map
5will store upto N elements inline, and will spill over to a heap.
6This unordered_map 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 "fl/stl/align.h"
14#include "fl/stl/private.h" // IWYU pragma: keep
15#include "fl/stl/hash.h"
16#include "fl/stl/int.h"
17#include "fl/math/math.h"
18#include "fl/stl/assert.h" // IWYU pragma: keep
19#include "fl/stl/bit_cast.h"
20#include "fl/stl/bitset.h"
21#include "fl/stl/move.h"
22#include "fl/stl/optional.h"
23#include "fl/stl/pair.h"
24#include "fl/stl/type_traits.h"
25#include "fl/stl/vector.h"
26#include "fl/stl/initializer_list.h" // IWYU pragma: keep
28#include "fl/stl/noexcept.h"
29
32
33namespace fl {
34
35// // begin using declarations for stl compatibility
36// use fl::equal_to;
37// use fl::unordered_map;
38// // end using declarations for stl compatibility
39
42template <typename T> struct EqualTo {
43 bool operator()(const T &a, const T &b) const { return a == b; }
44};
46
47// -- unordered_map class
48// ------------------------------------------------------------- Begin unordered_map
49// class
50
51#ifndef FASTLED_HASHMAP_INLINED_COUNT
52#define FASTLED_HASHMAP_INLINED_COUNT 8
53#endif
54
55template <typename Key, typename T, typename Hash = Hash<Key>,
56 typename KeyEqual = EqualTo<Key>,
57 int INLINED_COUNT = FASTLED_HASHMAP_INLINED_COUNT>
59 public:
61 unordered_map(fl::size initial_capacity) : unordered_map(initial_capacity, 0.7f) {}
62
70
71 unordered_map(fl::size initial_capacity, float max_load)
72 : _buckets(next_power_of_two(initial_capacity)), _size(0),
73 _tombstones(0), _occupied(next_power_of_two(initial_capacity)),
74 _deleted(next_power_of_two(initial_capacity)) {
75 setLoadFactor(max_load);
76 }
77
78 // Copy constructor
80 : _buckets(other._buckets.size()), _size(0), _tombstones(0),
81 mLoadFactor(other.mLoadFactor), _occupied(other._buckets.size()),
82 _deleted(other._buckets.size()), _hash(other._hash), _equal(other._equal) {
83 // Copy all occupied entries using insert to properly hash them
84 for (fl::size i = 0; i < other._buckets.size(); ++i) {
85 if (other.is_occupied(i)) {
86 insert(other._buckets[i].key, other._buckets[i].value);
87 }
88 }
89 }
90
91 // Move constructor
93 : _buckets(fl::move(other._buckets)), _size(other._size),
94 _tombstones(other._tombstones), mLoadFactor(other.mLoadFactor),
95 _occupied(fl::move(other._occupied)), _deleted(fl::move(other._deleted)),
96 _hash(fl::move(other._hash)), _equal(fl::move(other._equal)) {
97 // Reset other to valid empty state
98 other._size = 0;
99 other._tombstones = 0;
100 }
101
102 // Range constructor
103 template<typename InputIt>
104 unordered_map(InputIt first, InputIt last)
106 insert(first, last);
107 }
108
109 // Initializer list constructor
110 unordered_map(fl::initializer_list<pair<Key, T>> init)
112 insert(init);
113 }
114
115 // Constructor with hash/equal/allocator parameters
116 unordered_map(fl::size n, const Hash& hf, const KeyEqual& eq)
119 _hash(hf), _equal(eq) {
120 setLoadFactor(0.7f);
121 }
122
123 // Copy assignment operator
125 if (this != &other) {
126 // Clear current content
127 clear();
128
129 // Resize if necessary
130 if (_buckets.size() != other._buckets.size()) {
131 _buckets.clear();
132 _buckets.assign(other._buckets.size(), Entry{});
133 _occupied.reset();
134 _occupied.resize(other._buckets.size());
135 _deleted.reset();
136 _deleted.resize(other._buckets.size());
137 }
138
139 // Copy all occupied entries
140 for (fl::size i = 0; i < other._buckets.size(); ++i) {
141 if (other.is_occupied(i)) {
142 _buckets[i] = other._buckets[i];
143 mark_occupied(i);
144 ++_size;
145 }
146 }
147
148 mLoadFactor = other.mLoadFactor;
149 _hash = other._hash;
150 _equal = other._equal;
151 }
152 return *this;
153 }
154
155 // Move assignment operator
157 if (this != &other) {
158 _buckets = fl::move(other._buckets);
159 _size = other._size;
160 _tombstones = other._tombstones;
161 mLoadFactor = other.mLoadFactor;
162 _occupied = fl::move(other._occupied);
163 _deleted = fl::move(other._deleted);
164 _hash = fl::move(other._hash);
165 _equal = fl::move(other._equal);
166
167 // Reset other to valid empty state
168 other._size = 0;
169 other._tombstones = 0;
170 }
171 return *this;
172 }
173
174 // Initializer list assignment operator
176 clear();
177 insert(init);
178 return *this;
179 }
180
181 void setLoadFactor(float f) {
182 f = fl::clamp(f, 0.f, 1.f);
183 mLoadFactor = fl::map_range<float, u8>(f, 0.f, 1.f, 0, 255);
184 }
185
186 // Iterator support.
187 struct iterator {
188 // Standard iterator typedefs
189 // using difference_type = std::ptrdiff_t;
190 using value_type = pair<const Key, T>; // Keep const Key as per standard
194
195 iterator() FL_NOEXCEPT : _map(nullptr), _idx(0) {}
196 iterator(unordered_map *m, fl::size idx) : _map(m), _idx(idx) {
198 }
199
201 auto &e = _map->_buckets[_idx];
202 // Entry and pair<const Key, T> have the same memory layout
203 // Safe to reinterpret since we only add const to Key
204 return *fl::bit_cast<value_type*>(&e);
205 }
206
208 return &(operator*());
209 }
210
212 ++_idx;
214 return *this;
215 }
216
218 iterator tmp = *this;
219 ++(*this);
220 return tmp;
221 }
222
223 bool operator==(const iterator &o) const {
224 return _map == o._map && _idx == o._idx;
225 }
226 bool operator!=(const iterator &o) const { return !(*this == o); }
227
229 if (!_map)
230 return;
231 fl::size cap = _map->_buckets.size();
232 while (_idx < cap && !_map->is_occupied(_idx))
233 ++_idx;
234 }
235
236 private:
238 fl::size _idx;
239 friend class unordered_map;
240 };
241
243 // Standard iterator typedefs
244 // using difference_type = std::ptrdiff_t;
246 using pointer = const value_type *;
247 using reference = const value_type &;
249
251 const_iterator(const unordered_map *m, fl::size idx) : _map(m), _idx(idx) {
253 }
254 const_iterator(const iterator &it) : _map(it._map), _idx(it._idx) {}
255
257 auto &e = _map->_buckets[_idx];
258 // Entry and pair<const Key, T> have the same memory layout
259 // Safe to reinterpret since we only add const to Key
261 }
262
264 return &(operator*());
265 }
266
268 ++_idx;
270 return *this;
271 }
272
274 const_iterator tmp = *this;
275 ++(*this);
276 return tmp;
277 }
278
279 bool operator==(const const_iterator &o) const {
280 return _map == o._map && _idx == o._idx;
281 }
282 bool operator!=(const const_iterator &o) const { return !(*this == o); }
283
285 if (!_map)
286 return;
287 fl::size cap = _map->_buckets.size();
288 while (_idx < cap && !_map->is_occupied(_idx))
289 ++_idx;
290 }
291
292 friend class unordered_map;
293
294 private:
296 fl::size _idx;
297 };
298
299 iterator begin() { return iterator(this, 0); }
300 iterator end() { return iterator(this, _buckets.size()); }
301 const_iterator begin() const { return const_iterator(this, 0); }
302 const_iterator end() const { return const_iterator(this, _buckets.size()); }
303 const_iterator cbegin() const { return const_iterator(this, 0); }
304 const_iterator cend() const {
305 return const_iterator(this, _buckets.size());
306 }
307
308 static bool NeedsRehash(fl::size size, fl::size bucket_size, fl::size tombstones,
309 u8 load_factor) {
310 // (size + tombstones) << 8 : multiply numerator by 256
311 // capacity * max_load : denominator * threshold
312 u32 lhs = (size + tombstones) << 8;
313 u32 rhs = (bucket_size * load_factor);
314 return lhs > rhs;
315 }
316
317 // returns true if (size + tombs)/capacity > _max_load/256
318 bool needs_rehash() const {
320 }
321
322 // insert or overwrite - returns pair<iterator, bool>
323 // iterator points to element, bool is true if inserted, false if updated
324 pair<iterator, bool> insert(const Key &key, const T &value) {
325 const bool will_rehash = needs_rehash();
326 if (will_rehash) {
327 // if half the buckets are tombstones, rehash inline to prevent
328 // memory spill over into the heap.
329 if (_tombstones > _size) {
331 } else {
332 rehash_internal(_buckets.size() * 2);
333 }
334 }
335 fl::size idx;
336 bool is_new;
338 idx = p.first;
339 is_new = p.second;
340 if (is_new) {
341 _buckets[idx].key = key;
342 _buckets[idx].value = value;
343 mark_occupied(idx);
344 ++_size;
345 } else {
346 FASTLED_ASSERT(idx != npos(), "unordered_map::insert: invalid index at "
347 << idx << " which is " << npos());
348 _buckets[idx].value = value;
349 }
350 return {iterator(this, idx), is_new};
351 }
352
353 // Move version of insert - returns pair<iterator, bool>
354 // iterator points to element, bool is true if inserted, false if updated
356 const bool will_rehash = needs_rehash();
357 if (will_rehash) {
358 // if half the buckets are tombstones, rehash inline to prevent
359 // memory spill over into the heap.
360 if (_tombstones > _size) {
362 } else {
363 rehash_internal(_buckets.size() * 2);
364 }
365 }
366 fl::size idx;
367 bool is_new;
369 idx = p.first;
370 is_new = p.second;
371 if (is_new) {
372 _buckets[idx].key = fl::move(key);
373 _buckets[idx].value = fl::move(value);
374 mark_occupied(idx);
375 ++_size;
376 } else {
377 FASTLED_ASSERT(idx != npos(), "unordered_map::insert: invalid index at "
378 << idx << " which is " << npos());
379 _buckets[idx].value = fl::move(value);
380 }
381 return {iterator(this, idx), is_new};
382 }
383
384 // Pair-based insert (const) - std::unordered_map compatible
385 // Insert a pair<const Key, T> or pair<Key, T>
387 return insert(kv.first, kv.second);
388 }
389
390 // Pair-based insert (move) - std::unordered_map compatible
391 // Insert a pair<const Key, T> or pair<Key, T> with move semantics
393 return insert(fl::move(kv.first), fl::move(kv.second));
394 }
395
396 // Range insert - insert elements from iterator range [first, last)
397 // std::unordered_map compatible
398 template<typename InputIt>
399 void insert(InputIt first, InputIt last) {
400 for (; first != last; ++first) {
401 insert(*first); // Uses pair-based insert
402 }
403 }
404
405 // Initializer list insert - std::unordered_map compatible
406 // Insert elements from an initializer list like: m.insert({{1, "a"}, {2, "b"}})
407 void insert(fl::initializer_list<pair<Key, T>> init) {
408 for (const auto& kv : init) {
409 insert(kv); // Uses pair-based insert
410 }
411 }
412
413 // insert_or_assign() - C++17: insert new element or update existing
414 // Returns pair<iterator, bool> where bool is true if inserted, false if updated
416 const bool will_rehash = needs_rehash();
417 if (will_rehash) {
418 if (_tombstones > _size) {
420 } else {
421 rehash_internal(_buckets.size() * 2);
422 }
423 }
424 fl::size idx;
425 bool is_new;
427 idx = p.first;
428 is_new = p.second;
429 if (is_new) {
430 _buckets[idx].key = key;
431 _buckets[idx].value = fl::move(value);
432 mark_occupied(idx);
433 ++_size;
434 } else {
435 FASTLED_ASSERT(idx != npos(), "unordered_map::insert_or_assign: invalid index");
436 _buckets[idx].value = fl::move(value);
437 }
438 return {iterator(this, idx), is_new};
439 }
440
441 // insert_or_assign() with move key - C++17
443 const bool will_rehash = needs_rehash();
444 if (will_rehash) {
445 if (_tombstones > _size) {
447 } else {
448 rehash_internal(_buckets.size() * 2);
449 }
450 }
451 fl::size idx;
452 bool is_new;
454 idx = p.first;
455 is_new = p.second;
456 if (is_new) {
457 _buckets[idx].key = fl::move(key);
458 _buckets[idx].value = fl::move(value);
459 mark_occupied(idx);
460 ++_size;
461 } else {
462 FASTLED_ASSERT(idx != npos(), "unordered_map::insert_or_assign: invalid index");
463 _buckets[idx].value = fl::move(value);
464 }
465 return {iterator(this, idx), is_new};
466 }
467
468 // emplace() - construct element in-place from arguments
469 // For pairs, accepts (key, value) arguments to construct pair<Key, T>
470 template<typename... Args>
472 // Construct a pair from the arguments
474 // Use move-based insert since we just constructed this pair
475 return insert(fl::move(kv));
476 }
477
478 // emplace_hint() - construct element in-place with hint
479 // Hint is ignored for hash maps (no spatial locality benefits)
480 template<typename... Args>
481 iterator emplace_hint(const_iterator hint, Args&&... args) {
482 (void)hint; // Hint is ignored in hash maps
483 return emplace(fl::forward<Args>(args)...).first;
484 }
485
486 // try_emplace() - C++17: emplace only if key doesn't exist
487 // Advantage: doesn't move key if element already exists
488 template<typename... Args>
490 const bool will_rehash = needs_rehash();
491 if (will_rehash) {
492 if (_tombstones > _size) {
494 } else {
495 rehash_internal(_buckets.size() * 2);
496 }
497 }
498 fl::size idx;
499 bool is_new;
501 idx = p.first;
502 is_new = p.second;
503 if (is_new) {
504 // Key doesn't exist, construct in place
505 _buckets[idx].key = k;
506 _buckets[idx].value = T(fl::forward<Args>(args)...);
507 mark_occupied(idx);
508 ++_size;
509 }
510 // If not new, key already exists - don't construct value, just return existing iterator
511 return {iterator(this, idx), is_new};
512 }
513
514 // try_emplace() with move key - C++17
515 template<typename... Args>
517 const bool will_rehash = needs_rehash();
518 if (will_rehash) {
519 if (_tombstones > _size) {
521 } else {
522 rehash_internal(_buckets.size() * 2);
523 }
524 }
525 fl::size idx;
526 bool is_new;
528 idx = p.first;
529 is_new = p.second;
530 if (is_new) {
531 // Key doesn't exist, construct in place with moved key
532 _buckets[idx].key = fl::move(k);
533 _buckets[idx].value = T(fl::forward<Args>(args)...);
534 mark_occupied(idx);
535 ++_size;
536 }
537 // If not new, key already exists - don't move key or construct value
538 return {iterator(this, idx), is_new};
539 }
540
541 // remove key; returns true if removed
542 bool remove(const Key &key) {
543 auto idx = find_index(key);
544 if (idx == npos())
545 return false;
546 mark_deleted(idx);
547 --_size;
548 ++_tombstones;
549 return true;
550 }
551
552 bool erase(const Key &key) { return remove(key); }
553
554 // Iterator-based erase - more efficient when you already have the iterator position
555 iterator erase(iterator it) {
556 if (it == end() || it._map != this) {
557 return end(); // Invalid iterator
558 }
559
560 // Mark the current position as deleted
561 mark_deleted(it._idx);
562 --_size;
563 ++_tombstones;
564
565 // Advance to next valid element and return iterator to it
566 ++it._idx;
567 it.advance_to_occupied();
568 return it;
569 }
570
571 // Range erase - erase elements in range [first, last)
572 iterator erase(const_iterator first, const_iterator last) {
573 if (first._map != this || last._map != this) {
574 return end(); // Invalid iterators
575 }
576
577 // Erase each element in the range
578 // We need to convert const_iterator to iterator for the return value
579 iterator current(this, first._idx);
580 current.advance_to_occupied();
581
582 while (current != end() && current._idx < last._idx) {
583 fl::size idx_to_erase = current._idx;
584 ++current;
585 current.advance_to_occupied();
586
587 // Erase the element at idx_to_erase
588 mark_deleted(idx_to_erase);
589 --_size;
590 ++_tombstones;
591 }
592
593 return current;
594 }
595
596 void clear() {
597 _buckets.assign(_buckets.size(), Entry{});
598 _occupied.reset();
599 _deleted.reset();
600 _size = _tombstones = 0;
601 }
602
603 // swap() - swap contents with another unordered_map
604 void swap(unordered_map& other) {
605 // Swap all member variables
606 _buckets.swap(other._buckets);
607 fl::swap(_size, other._size);
611 fl::swap(_deleted, other._deleted);
612 fl::swap(_hash, other._hash);
613 fl::swap(_equal, other._equal);
614 }
615
616 // find pointer to value or nullptr
617 T *find_value(const Key &key) {
618 auto idx = find_index(key);
619 return idx == npos() ? nullptr : &_buckets[idx].value;
620 }
621
622 const T *find_value(const Key &key) const {
623 auto idx = find_index(key);
624 return idx == npos() ? nullptr : &_buckets[idx].value;
625 }
626
627 iterator find(const Key &key) {
628 auto idx = find_index(key);
629 return idx == npos() ? end() : iterator(this, idx);
630 }
631
632 const_iterator find(const Key &key) const {
633 auto idx = find_index(key);
634 return idx == npos() ? end() : const_iterator(this, idx);
635 }
636
637 bool contains(const Key &key) const {
638 auto idx = find_index(key);
639 return idx != npos();
640 }
641
642 // at() - bounds-checked access, asserts if key not found
643 T &at(const Key &key) {
644 T* value = find_value(key);
645 FASTLED_ASSERT(value != nullptr, "unordered_map::at: key not found");
646 return *value;
647 }
648
649 const T &at(const Key &key) const {
650 const T* value = find_value(key);
651 FASTLED_ASSERT(value != nullptr, "unordered_map::at: key not found");
652 return *value;
653 }
654
655 // count() - returns 0 or 1 (unordered_map has unique keys)
656 fl::size count(const Key &key) const {
657 return contains(key) ? 1 : 0;
658 }
659
660 // equal_range() - returns pair of iterators [it, it+1) or [end, end)
662 iterator it = find(key);
663 if (it == end()) {
664 return {end(), end()};
665 }
666 iterator next = it;
667 ++next;
668 return {it, next};
669 }
670
672 const_iterator it = find(key);
673 if (it == end()) {
674 return {end(), end()};
675 }
676 const_iterator next = it;
677 ++next;
678 return {it, next};
679 }
680
681 // access or default-construct
682 T &operator[](const Key &key) {
683 fl::size idx;
684 bool is_new;
685
687 idx = p.first;
688 is_new = p.second;
689
690 // Check if find_slot failed to find a valid slot (unordered_map is full)
691 if (idx == npos()) {
692 // Need to resize to make room
693 if (needs_rehash()) {
694 // if half the buckets are tombstones, rehash inline to prevent
695 // memory growth. Otherwise, double the size.
696 if (_tombstones >= _buckets.size() / 2) {
698 } else {
699 rehash_internal(_buckets.size() * 2);
700 }
701 } else {
702 // Force a rehash with double size if needs_rehash() doesn't detect the issue
703 rehash_internal(_buckets.size() * 2);
704 }
705
706 // Try find_slot again after resize
707 p = find_slot(key);
708 idx = p.first;
709 is_new = p.second;
710
711 // If still npos() after resize, allocation failed catastrophically
712 if (idx == npos()) {
713 // This should never happen after a successful resize
714 static T default_value{};
715 FASTLED_ASSERT(false, "unordered_map::operator[]: Failed to allocate after rehash");
716 return default_value;
717 }
718 }
719
720 if (is_new) {
721 _buckets[idx].key = key;
722 _buckets[idx].value = T{};
723 mark_occupied(idx);
724 ++_size;
725 }
726 return _buckets[idx].value;
727 }
728
729 fl::size size() const { return _size; }
730 bool empty() const { return _size == 0; }
731 fl::size capacity() const { return _buckets.size(); }
732
733 // max_size() - theoretical maximum size
734 fl::size max_size() const {
735 // Return the maximum value that fl::size can hold, divided by the size of Entry
736 // to be conservative about memory limits
737 return static_cast<fl::size>(-1) / sizeof(Entry);
738 }
739
740 // hash_function() - return the hash functor
741 Hash hash_function() const { return _hash; }
742
743 // key_eq() - return the key equality predicate
744 KeyEqual key_eq() const { return _equal; }
745
746 // Hash Policy Interface
747
748 // load_factor() - current load factor (size / bucket_count)
749 float load_factor() const {
750 if (_buckets.size() == 0) {
751 return 0.0f;
752 }
753 return static_cast<float>(_size) / static_cast<float>(_buckets.size());
754 }
755
756 // max_load_factor() - get maximum load factor
757 float max_load_factor() const {
758 // mLoadFactor is stored as u8 (0-255) representing 0.0-1.0
759 return static_cast<float>(mLoadFactor) / 255.0f;
760 }
761
762 // max_load_factor(float ml) - set maximum load factor
763 void max_load_factor(float ml) {
764 setLoadFactor(ml);
765 }
766
767 // bucket_count() - number of buckets
768 fl::size bucket_count() const {
769 return _buckets.size();
770 }
771
772 // rehash(size_type n) - change number of buckets to at least n
773 void rehash(fl::size n) {
774 // Ensure n is at least as large as the current number of elements
775 fl::size min_buckets = _size;
776 if (n < min_buckets) {
777 n = min_buckets;
778 }
779
780 // Only rehash if we need more buckets than we currently have
781 if (n > _buckets.size()) {
783 }
784 // If n <= current bucket count, do nothing (std::unordered_map behavior)
785 }
786
787 // reserve(size_type n) - reserve capacity for at least n elements
788 void reserve(fl::size n) {
789 // Calculate required buckets to hold n elements without exceeding load factor
790 float max_lf = max_load_factor();
791 if (max_lf <= 0.0f) {
792 max_lf = 0.7f; // Default if somehow zero
793 }
794 // Required buckets = ceil(n / max_load_factor)
795 fl::size required_buckets = static_cast<fl::size>(
796 static_cast<float>(n) / max_lf + 0.999999f // Add almost 1 to simulate ceil
797 );
798 if (required_buckets > _buckets.size()) {
799 rehash(required_buckets);
800 }
801 }
802
803 private:
804 static fl::size npos() {
805 return static_cast<fl::size>(-1);
806 }
807
808 // Helper methods to check entry state
809 bool is_occupied(fl::size idx) const { return _occupied.test(idx); }
810
811 bool is_deleted(fl::size idx) const { return _deleted.test(idx); }
812
813 bool is_empty(fl::size idx) const {
814 return !is_occupied(idx) && !is_deleted(idx);
815 }
816
817 void mark_occupied(fl::size idx) {
818 _occupied.set(idx);
819 _deleted.reset(idx);
820 }
821
822 void mark_deleted(fl::size idx) {
823 _occupied.reset(idx);
824 _deleted.set(idx);
825 }
826
827 void mark_empty(fl::size idx) {
828 _occupied.reset(idx);
829 _deleted.reset(idx);
830 }
831
833 struct FL_ALIGN_AS_T(EntryAlign::value) Entry {
834 Key key;
835 T value;
836 void swap(Entry &other) {
837 fl::swap(key, other.key);
838 fl::swap(value, other.value);
839 }
840 };
841
842 static fl::size next_power_of_two(fl::size n) {
843 fl::size p = 1;
844 while (p < n)
845 p <<= 1;
846 return p;
847 }
848
850 const fl::size cap = _buckets.size();
851 const fl::size mask = cap - 1;
852 const fl::size h = _hash(key) & mask;
853 fl::size first_tomb = npos();
854
855 if (cap <= 8) {
856 // linear probing
857 for (fl::size i = 0; i < cap; ++i) {
858 const fl::size idx = (h + i) & mask;
859
860 if (is_empty(idx))
861 return {first_tomb != npos() ? first_tomb : idx, true};
862 if (is_deleted(idx)) {
863 if (first_tomb == npos())
864 first_tomb = idx;
865 } else if (is_occupied(idx) && _equal(_buckets[idx].key, key)) {
866 return {idx, false};
867 }
868 }
869 } else {
870 // quadratic probing up to 8 tries
871 fl::size i = 0;
872 for (; i < 8; ++i) {
873 const fl::size idx = (h + i + i * i) & mask;
874
875 if (is_empty(idx))
876 return {first_tomb != npos() ? first_tomb : idx, true};
877 if (is_deleted(idx)) {
878 if (first_tomb == npos())
879 first_tomb = idx;
880 } else if (is_occupied(idx) && _equal(_buckets[idx].key, key)) {
881 return {idx, false};
882 }
883 }
884 // fallback to linear for the rest
885 for (; i < cap; ++i) {
886 const fl::size idx = (h + i) & mask;
887
888 if (is_empty(idx))
889 return {first_tomb != npos() ? first_tomb : idx, true};
890 if (is_deleted(idx)) {
891 if (first_tomb == npos())
892 first_tomb = idx;
893 } else if (is_occupied(idx) && _equal(_buckets[idx].key, key)) {
894 return {idx, false};
895 }
896 }
897 }
898
899 return {npos(), false};
900 }
901
902 enum {
905 };
906
907 fl::size find_index(const Key &key) const {
908 const fl::size cap = _buckets.size();
909 const fl::size mask = cap - 1;
910 const fl::size h = _hash(key) & mask;
911
912 if (cap <= kLinearProbingOnlySize) {
913 // linear probing
914 for (fl::size i = 0; i < cap; ++i) {
915 const fl::size idx = (h + i) & mask;
916 if (is_empty(idx))
917 return npos();
918 if (is_occupied(idx) && _equal(_buckets[idx].key, key))
919 return idx;
920 }
921 } else {
922 // quadratic probing up to 8 tries
923 fl::size i = 0;
924 for (; i < kQuadraticProbingTries; ++i) {
925 const fl::size idx = (h + i + i * i) & mask;
926 if (is_empty(idx))
927 return npos();
928 if (is_occupied(idx) && _equal(_buckets[idx].key, key))
929 return idx;
930 }
931 // fallback to linear for the rest
932 for (; i < cap; ++i) {
933 const fl::size idx = (h + i) & mask;
934 if (is_empty(idx))
935 return npos();
936 if (is_occupied(idx) && _equal(_buckets[idx].key, key))
937 return idx;
938 }
939 }
940
941 return npos();
942 }
943
945 const Key &key, const fl::bitset<1024> &occupied_set) const {
946 const fl::size cap = _buckets.size();
947 const fl::size mask = cap - 1;
948 const fl::size h = _hash(key) & mask;
949
950 if (cap <= kLinearProbingOnlySize) {
951 // linear probing
952 for (fl::size i = 0; i < cap; ++i) {
953 const fl::size idx = (h + i) & mask;
954 bool occupied = occupied_set.test(idx);
955 if (occupied) {
956 continue;
957 }
958 return idx;
959 }
960 } else {
961 // quadratic probing up to 8 tries
962 fl::size i = 0;
963 for (; i < kQuadraticProbingTries; ++i) {
964 const fl::size idx = (h + i + i * i) & mask;
965 bool occupied = occupied_set.test(idx);
966 if (occupied) {
967 continue;
968 }
969 return idx;
970 }
971 // fallback to linear for the rest
972 for (; i < cap; ++i) {
973 const fl::size idx = (h + i) & mask;
974 bool occupied = occupied_set.test(idx);
975 if (occupied) {
976 continue;
977 }
978 return idx;
979 }
980 }
981 return npos();
982 }
983
984 void rehash_internal(fl::size new_cap) {
985 new_cap = next_power_of_two(new_cap);
987 fl::bitset<1024> old_occupied = _occupied;
988
989 _buckets.swap(old);
990 _buckets.clear();
991 _buckets.assign(new_cap, Entry{});
992
993 _occupied.reset();
994 _occupied.resize(new_cap);
995 _deleted.reset();
996 _deleted.resize(new_cap);
997
998 _size = _tombstones = 0;
999
1000 for (fl::size i = 0; i < old.size(); i++) {
1001 if (old_occupied.test(i))
1002 insert(fl::move(old[i].key), fl::move(old[i].value));
1003 }
1004 }
1005
1006 // Rehash the inline buckets without resizing
1010 // filter out tombstones and compact
1011 fl::size cap = _buckets.size();
1012 fl::size pos = 0;
1013 // compact live elements to the left.
1014 for (fl::size i = 0; i < cap; ++i) {
1015 if (is_occupied(i)) {
1016 if (pos != i) {
1018 mark_empty(i);
1020 }
1021 ++pos;
1022 } else if (is_deleted(i)) {
1023 mark_empty(i);
1024 }
1025 }
1026
1027 fl::bitset<1024> occupied; // Preallocate a bitset of size 1024
1028 // swap the components, this will happen at most N times,
1029 // use the occupied bitset to track which entries are occupied
1030 // in the array rather than just copied in.
1032 for (fl::size i = 0; i < _size; ++i) {
1033 const bool already_finished = occupied.test(i);
1034 if (already_finished) {
1035 continue;
1036 }
1037 auto &e = _buckets[i];
1038 // FASTLED_ASSERT(e.state == EntryState::Occupied,
1039 // "unordered_map::rehash_inline_no_resize: invalid
1040 // state");
1041
1042 fl::size idx = find_unoccupied_index_using_bitset(e.key, occupied);
1043 if (idx == npos()) {
1044 // no more space
1045 FASTLED_ASSERT(
1046 false, "unordered_map::rehash_inline_no_resize: invalid index at "
1047 << idx << " which is " << npos());
1048 return;
1049 }
1050 // if idx < pos then we are moving the entry to a new location
1051 FASTLED_ASSERT(!tmp,
1052 "unordered_map::rehash_inline_no_resize: invalid tmp");
1053 if (idx >= _size) {
1054 // directly move it now
1055 _buckets[idx] = fl::move(e);
1056 continue;
1057 }
1058 tmp = fl::move(e);
1059 occupied.set(idx);
1060 _buckets[idx] = fl::move(*tmp.ptr());
1061 while (!tmp.empty()) {
1062 // we have to find a place for temp.
1063 // find new position for tmp.
1064 auto key = tmp.ptr()->key;
1065 fl::size new_idx =
1067 if (new_idx == npos()) {
1068 // no more space
1069 FASTLED_ASSERT(
1070 false,
1071 "unordered_map::rehash_inline_no_resize: invalid index at "
1072 << new_idx << " which is " << npos());
1073 return;
1074 }
1075 occupied.set(new_idx);
1076 if (new_idx < _size) {
1077 // we have to swap the entry at new_idx with tmp
1078 fl::optional<Entry> tmp2 = fl::move(_buckets[new_idx]);
1079 _buckets[new_idx] = fl::move(*tmp.ptr());
1080 tmp = fl::move(tmp2);
1081 } else {
1082 // we can just move tmp to new_idx
1083 _buckets[new_idx] = fl::move(*tmp.ptr());
1084 tmp.reset();
1085 }
1086 }
1087 FASTLED_ASSERT(
1088 occupied.test(i),
1089 "unordered_map::rehash_inline_no_resize: invalid occupied at " << i);
1090 FASTLED_ASSERT(
1091 tmp.empty(), "unordered_map::rehash_inline_no_resize: invalid tmp at " << i);
1092 }
1093 // Reset tombstones count since we've cleared all deleted entries
1094 _tombstones = 0;
1095 }
1097
1098 public:
1100 bool operator==(const unordered_map& other) const {
1101 if (size() != other.size()) return false;
1102 for (const auto& kv : *this) {
1103 auto it = other.find(kv.first);
1104 if (it == other.end() || it->second != kv.second) return false;
1105 }
1106 return true;
1107 }
1108
1110 bool operator!=(const unordered_map& other) const {
1111 return !(*this == other);
1112 }
1113
1114 memory_resource* get_memory_resource() const { return _buckets.get_resource(); }
1115
1116 private:
1118 fl::size _size;
1119 fl::size _tombstones;
1121 fl::bitset<1024> _occupied;
1122 fl::bitset<1024> _deleted;
1124 KeyEqual _equal;
1125};
1126
1127// begin using declarations for stl compatibility
1128template <typename T> using equal_to = EqualTo<T>;
1129
1130// Legacy alias for backwards compatibility - use fl::unordered_map instead
1131template <typename Key, typename T, typename Hash = Hash<Key>,
1132 typename KeyEqual = equal_to<Key>>
1134// end using declarations for stl compatibility
1135
1136} // namespace fl
1137
uint8_t pos
Definition Blur.ino:11
Alignment macros and utilities for FastLED.
T * ptr() FL_NOEXCEPT
Definition optional.h:43
bool empty() const FL_NOEXCEPT
Definition optional.h:41
void reset() FL_NOEXCEPT
Definition optional.h:46
Polymorphic memory resource base class (PMR-style).
fl::size find_index(const vec2< u16 > &key) const
static fl::size npos()
bool operator==(const unordered_map &other) const
Equality comparison (map equality: same key-value pairs, order-independent)
unordered_map & operator=(unordered_map &&other) FL_NOEXCEPT
void reserve(fl::size n)
Hash< vec2< u16 > > _hash
fl::bitset< 1024 > _occupied
pair< const_iterator, const_iterator > equal_range(const Key &key) const
unordered_map(fl::size n, const Hash &hf, const KeyEqual &eq)
void mark_occupied(fl::size idx)
iterator find(const Key &key)
bool operator!=(const unordered_map &other) const
Inequality comparison.
unordered_map(fl::size initial_capacity)
bool is_deleted(fl::size idx) const
unordered_map & operator=(fl::initializer_list< pair< Key, T > > init) FL_NOEXCEPT
iterator emplace_hint(const_iterator hint, Args &&... args)
iterator erase(iterator it)
fl::size count(const Key &key) const
void insert(InputIt first, InputIt last)
const T & at(const Key &key) const
FL_DISABLE_WARNING_PUSH FL_DISABLE_WARNING_NULL_DEREFERENCE void rehash_inline_no_resize()
bool is_empty(fl::size idx) const
unordered_map(fl::initializer_list< pair< Key, T > > init)
void mark_deleted(fl::size idx)
float load_factor() const
bool remove(const Key &key)
const T * find_value(const Key &key) const
EqualTo< vec2< u16 > > _equal
unordered_map() FL_NOEXCEPT
void insert(fl::initializer_list< pair< Key, T > > init)
fl::vector_inlined< Entry, FASTLED_HASHMAP_INLINED_COUNT > _buckets
bool empty() const
pair< fl::size, bool > find_slot(const vec2< u16 > &key) const
fl::size find_unoccupied_index_using_bitset(const Key &key, const fl::bitset< 1024 > &occupied_set) const
pair< iterator, bool > emplace(Args &&... args)
unordered_map(memory_resource *resource)
void rehash(fl::size n)
fl::size capacity() const
T * find_value(const Key &key)
unordered_map(const unordered_map &other) FL_NOEXCEPT
const_iterator cbegin() const
pair< iterator, bool > try_emplace(const Key &k, Args &&... args)
iterator erase(const_iterator first, const_iterator last)
static bool NeedsRehash(fl::size size, fl::size bucket_size, fl::size tombstones, u8 load_factor)
struct FL_ALIGN_AS_T(EntryAlign::value) Entry
fl::size bucket_count() const
float max_load_factor() const
pair< iterator, bool > try_emplace(Key &&k, Args &&... args)
unordered_map & operator=(const unordered_map &other) FL_NOEXCEPT
bool erase(const Key &key)
pair< iterator, bool > insert_or_assign(Key &&key, T &&value)
static fl::size next_power_of_two(fl::size n)
bool is_occupied(fl::size idx) const
pair< iterator, bool > insert(const vec2< u16 > &key, const u8 &value)
unordered_map(unordered_map &&other) FL_NOEXCEPT
bool needs_rehash() const
const_iterator begin() const
T & at(const Key &key)
pair< iterator, bool > insert_or_assign(const Key &key, T &&value)
max_align< vec2< u16 >, u8 > EntryAlign
const_iterator cend() const
unordered_map(InputIt first, InputIt last)
void swap(unordered_map &other)
fl::size max_size() const
const_iterator end() const
void rehash_internal(fl::size new_cap)
pair< iterator, bool > insert(Key &&key, T &&value)
bool contains(const Key &key) const
memory_resource * get_memory_resource() const
void mark_empty(fl::size idx)
const_iterator find(const Key &key) const
void max_load_factor(float ml)
pair< iterator, iterator > equal_range(const Key &key)
fl::size size() const
fl::size _tombstones
fl::bitset< 1024 > _deleted
pair< iterator, bool > insert(pair< Key, T > &&kv)
pair< iterator, bool > insert(const pair< Key, T > &kv)
T & operator[](const Key &key)
Hash hash_function() const
void setLoadFactor(float f)
unordered_map(fl::size initial_capacity, float max_load)
KeyEqual key_eq() const
fl::size size() const FL_NOEXCEPT
PMR-style polymorphic memory resource for type-erased allocation.
constexpr T && forward(typename remove_reference< T >::type &t) FL_NOEXCEPT
Definition s16x16x4.h:234
constexpr remove_reference< T >::type && move(T &&t) FL_NOEXCEPT
Definition s16x16x4.h:28
constexpr remove_reference< T >::type && move(T &&t) FL_NOEXCEPT
Definition move.h:28
unsigned char u8
Definition stdint.h:131
constexpr int type_rank< T >::value
unordered_map< Key, T, Hash, KeyEqual > hash_map
void init(Context &ctx, int w, int h)
Definition engine.h:133
FASTLED_FORCE_INLINE U map_range(T value, T in_min, T in_max, U out_min, U out_max) FL_NOEXCEPT
Definition math.h:174
Optional< T > optional
Definition optional.h:16
void swap(array< T, N > &lhs, array< T, N > &rhs) FL_NOEXCEPT
Definition array.h:209
EqualTo< T > equal_to
VectorN< T, INLINED_SIZE > vector_inlined
Definition vector.h:1133
To bit_cast(const From &from) FL_NOEXCEPT
Definition bit_cast.h:48
constexpr enable_if< is_fixed_point< T >::value, T >::type clamp(T x, T lo, T hi) FL_NOEXCEPT
Base definition for an LED controller.
Definition crgb.hpp:179
corkscrew_args args
Definition old.h:149
#define FL_DISABLE_WARNING_PUSH
#define FL_DISABLE_WARNING_SHORTEN_64_TO_32
#define FL_DISABLE_WARNING_NULL_DEREFERENCE
#define FL_DISABLE_WARNING_POP
#define FL_NOEXCEPT
#define FL_ALIGN
Definition Keyboard.h:22
bool operator()(const T &a, const T &b) const
T1 first
Definition pair.h:16
T2 second
Definition pair.h:17
const_iterator(const unordered_map *m, fl::size idx)
fl::forward_iterator_tag iterator_category
bool operator==(const const_iterator &o) const
bool operator!=(const const_iterator &o) const
bool operator==(const iterator &o) const
fl::forward_iterator_tag iterator_category
reference operator*() const
iterator(unordered_map *m, fl::size idx)
bool operator!=(const iterator &o) const
pair< const Key, T > value_type
#define FASTLED_HASHMAP_INLINED_COUNT