FastLED 3.9.15
Loading...
Searching...
No Matches
unordered_map_small.h
Go to the documentation of this file.
1#pragma once
2
3#include "fl/stl/stdint.h"
4#include "fl/stl/assert.h"
5#include "fl/stl/algorithm.h"
6#include "fl/stl/pair.h"
7#include "fl/stl/vector.h"
8#include "fl/stl/allocator.h"
11#include "fl/stl/move.h"
12#include "fl/stl/noexcept.h"
13#include "platforms/assert_defs.h"
14
15namespace fl {
16
17// Lightweight equal_to functor — avoids pulling in hash infrastructure.
18template <typename T> struct SmallMapEqualTo {
19 bool operator()(const T& a, const T& b) const FL_NOEXCEPT { return a == b; }
20};
21
22// Small unordered map using linear scan with equality comparison.
23//
24// No hashing — flat vector of key-value pairs with O(n) lookup.
25// Uses an occupied bitmap so erase is O(1) (just clears a bit) and
26// iterators remain stable across erase. Freed slots are reused by insert.
27//
28// Optimal for small collections (< ~32 elements) where hash overhead
29// isn't worth it. Minimal code size, excellent cache locality.
30template <typename Key, typename Value,
31 typename Equal = fl::SmallMapEqualTo<Key>>
33 public:
34 enum insert_result { inserted = 0, exists = 1, at_capacity = 2 };
35
36 using key_type = Key;
37 using mapped_type = Value;
39 using size_type = fl::size;
40 using key_equal = Equal;
42
43 // Forward iterator that skips unoccupied slots.
44 struct iterator {
48
49 iterator() FL_NOEXCEPT : mMap(nullptr), mIdx(0) {}
52
53 reference operator*() const FL_NOEXCEPT { return mMap->mData[mIdx]; }
54 pointer operator->() const FL_NOEXCEPT { return &mMap->mData[mIdx]; }
55
57 iterator operator++(int) FL_NOEXCEPT { iterator t = *this; ++(*this); return t; }
58
59 bool operator==(const iterator& o) const FL_NOEXCEPT { return mIdx == o.mIdx; }
60 bool operator!=(const iterator& o) const FL_NOEXCEPT { return mIdx != o.mIdx; }
61
62 private:
64 if (!mMap) return;
65 size_type cap = mMap->mData.size();
67 }
70 friend class unordered_map_small;
71 };
72
74 using reference = const value_type&;
75 using pointer = const value_type*;
77
78 const_iterator() FL_NOEXCEPT : mMap(nullptr), mIdx(0) {}
82 : mMap(it.mMap), mIdx(it.mIdx) {}
83
84 reference operator*() const FL_NOEXCEPT { return mMap->mData[mIdx]; }
85 pointer operator->() const FL_NOEXCEPT { return &mMap->mData[mIdx]; }
86
88 const_iterator operator++(int) FL_NOEXCEPT { const_iterator t = *this; ++(*this); return t; }
89
90 bool operator==(const const_iterator& o) const FL_NOEXCEPT { return mIdx == o.mIdx; }
91 bool operator!=(const const_iterator& o) const FL_NOEXCEPT { return mIdx != o.mIdx; }
92
93 private:
95 if (!mMap) return;
96 size_type cap = mMap->mData.size();
98 }
102 };
103
104 private:
108 Equal mEqual;
110
111 // Linear scan for key among occupied slots. Returns slot index or npos().
113 for (size_type i = 0; i < mData.size(); ++i) {
114 if (mOccupied.test(i) && mEqual(mData[i].first, key)) {
115 return i;
116 }
117 }
118 return npos();
119 }
120
121 // Find first free (unoccupied) slot, or npos() if none.
123 for (size_type i = 0; i < mData.size(); ++i) {
124 if (!mOccupied.test(i)) return i;
125 }
126 return npos();
127 }
128
129 static size_type npos() FL_NOEXCEPT { return static_cast<size_type>(-1); }
130
131 // Place a value at a slot index (must already be allocated in mData).
133 mData[idx] = kv;
134 mOccupied.set(idx);
135 ++mSize;
136 }
137
139 mData[idx] = fl::move(kv);
140 mOccupied.set(idx);
141 ++mSize;
142 }
143
144 // Append a new slot at the end.
146 size_type idx = mData.size();
147 mData.push_back(kv);
148 mOccupied.resize(idx + 1);
149 mOccupied.set(idx);
150 ++mSize;
151 return idx;
152 }
153
155 size_type idx = mData.size();
156 mData.push_back(fl::move(kv));
157 mOccupied.resize(idx + 1);
158 mOccupied.set(idx);
159 ++mSize;
160 return idx;
161 }
162
163 // Insert into a free slot or append. Returns slot index.
166 if (free != npos()) {
167 place_at(free, kv);
168 return free;
169 }
170 return append(kv);
171 }
172
175 if (free != npos()) {
176 place_at(free, fl::move(kv));
177 return free;
178 }
179 return append(fl::move(kv));
180 }
181
182 public:
183 // Constructors
185
187 : mData(resource), mResource(resource) {}
188
189 explicit unordered_map_small(const Equal& eq,
190 memory_resource* resource = nullptr) FL_NOEXCEPT
191 : mData(resource ? resource : default_memory_resource()),
192 mEqual(eq),
193 mResource(resource) {}
194
196 : mData(other.mData), mOccupied(other.mOccupied),
197 mSize(other.mSize), mEqual(other.mEqual),
198 mResource(other.mResource) {}
199
201 if (this != &other) {
202 mData = other.mData;
203 mOccupied = other.mOccupied;
204 mSize = other.mSize;
205 mEqual = other.mEqual;
206 mResource = other.mResource;
207 }
208 return *this;
209 }
210
212 : mData(fl::move(other.mData)), mOccupied(fl::move(other.mOccupied)),
213 mSize(other.mSize), mEqual(fl::move(other.mEqual)),
214 mResource(other.mResource) {
215 other.mSize = 0;
216 }
217
219 if (this != &other) {
220 mData = fl::move(other.mData);
221 mOccupied = fl::move(other.mOccupied);
222 mSize = other.mSize;
223 mEqual = fl::move(other.mEqual);
224 mResource = other.mResource;
225 other.mSize = 0;
226 }
227 return *this;
228 }
229
230 // Iterators
231 iterator begin() FL_NOEXCEPT { return iterator(this, 0); }
232 iterator end() FL_NOEXCEPT { return iterator(this, mData.size()); }
233 const_iterator begin() const FL_NOEXCEPT { return const_iterator(this, 0); }
234 const_iterator end() const FL_NOEXCEPT { return const_iterator(this, mData.size()); }
235 const_iterator cbegin() const FL_NOEXCEPT { return const_iterator(this, 0); }
236 const_iterator cend() const FL_NOEXCEPT { return const_iterator(this, mData.size()); }
237
238 // Capacity
239 size_type size() const FL_NOEXCEPT { return mSize; }
240 bool empty() const FL_NOEXCEPT { return mSize == 0; }
241 size_type capacity() const FL_NOEXCEPT { return mData.capacity(); }
242 size_type max_size() const FL_NOEXCEPT { return mData.max_size(); }
243
244
245
246 // Element access
247 Value& operator[](const Key& key) FL_NOEXCEPT {
248 size_type idx = find_index(key);
249 if (idx != npos()) return mData[idx].second;
250 // Insert default-constructed value
251 value_type kv(key, Value());
252 idx = do_insert(fl::move(kv));
253 FASTLED_ASSERT(idx != npos(), "unordered_map_small::operator[]: insert failed");
254 return mData[idx].second;
255 }
256
257 Value& at(const Key& key) FL_NOEXCEPT {
258 size_type idx = find_index(key);
259 FASTLED_ASSERT(idx != npos(), "Key not found in unordered_map_small");
260 return mData[idx].second;
261 }
262
263 const Value& at(const Key& key) const FL_NOEXCEPT {
264 size_type idx = find_index(key);
265 FASTLED_ASSERT(idx != npos(), "Key not found in unordered_map_small");
266 return mData[idx].second;
267 }
268
269 // Lookup
271 size_type idx = find_index(key);
272 return idx != npos() ? iterator(this, idx) : end();
273 }
274
275 const_iterator find(const Key& key) const FL_NOEXCEPT {
276 size_type idx = find_index(key);
277 return idx != npos() ? const_iterator(this, idx) : end();
278 }
279
280 size_type count(const Key& key) const FL_NOEXCEPT {
281 return find_index(key) != npos() ? 1 : 0;
282 }
283
284 bool contains(const Key& key) const FL_NOEXCEPT {
285 return find_index(key) != npos();
286 }
287
288 bool has(const Key& key) const FL_NOEXCEPT { return contains(key); }
289
290 // Insertion — does NOT overwrite if key exists
292 size_type idx = find_index(kv.first);
293 if (idx != npos()) {
294 return fl::pair<iterator, bool>(iterator(this, idx), false);
295 }
296 idx = do_insert(kv);
297 return fl::pair<iterator, bool>(iterator(this, idx), true);
298 }
299
301 size_type idx = find_index(kv.first);
302 if (idx != npos()) {
303 return fl::pair<iterator, bool>(iterator(this, idx), false);
304 }
305 idx = do_insert(fl::move(kv));
306 return fl::pair<iterator, bool>(iterator(this, idx), true);
307 }
308
309 bool insert(const Key& key, const Value& value, insert_result* result = nullptr) FL_NOEXCEPT {
310 size_type idx = find_index(key);
311 if (idx != npos()) {
312 if (result) *result = exists;
313 return false;
314 }
316 if (result) *result = inserted;
317 return true;
318 }
319
320 bool insert(Key&& key, Value&& value, insert_result* result = nullptr) FL_NOEXCEPT {
321 Key key_copy = key;
322 size_type idx = find_index(key_copy);
323 if (idx != npos()) {
324 if (result) *result = exists;
325 return false;
326 }
328 if (result) *result = inserted;
329 return true;
330 }
331
332 // Emplace
333 template <typename... Args>
338
339 // Erase — clears occupied bit, iterator stable
341 if (pos.mMap != this || pos.mIdx >= mData.size()) return end();
342 if (!mOccupied.test(pos.mIdx)) return end(); // already erased
343 mOccupied.reset(pos.mIdx);
344 --mSize;
345 // Advance to next occupied slot
346 iterator next(this, pos.mIdx + 1);
347 return next;
348 }
349
351 iterator it(this, pos.mIdx);
352 return erase(it);
353 }
354
356 size_type idx = find_index(key);
357 if (idx != npos()) {
358 mOccupied.reset(idx);
359 --mSize;
360 return 1;
361 }
362 return 0;
363 }
364
365 // Clear
367 mData.clear();
369 mSize = 0;
370 }
371
372 // Swap
374 mData.swap(other.mData);
375 fl::swap(mOccupied, other.mOccupied);
376 fl::swap(mSize, other.mSize);
377 fl::swap(mEqual, other.mEqual);
378 fl::swap(mResource, other.mResource);
379 }
380
381 key_equal key_eq() const FL_NOEXCEPT { return mEqual; }
383
385 mData.reserve(n);
386 if (n > mOccupied.size()) mOccupied.resize(n);
387 }
388
389 // Remove unoccupied gaps, compacting the vector.
391 size_type write = 0;
392 for (size_type read = 0; read < mData.size(); ++read) {
393 if (mOccupied.test(read)) {
394 if (write != read) {
395 mData[write] = fl::move(mData[read]);
396 }
397 ++write;
398 }
399 }
400 // Trim vector to alive count
401 while (mData.size() > write) mData.pop_back();
402 // Rebuild bitmap — all slots occupied
403 mOccupied = bitset_dynamic(write);
404 for (size_type i = 0; i < write; ++i) {
405 mOccupied.set(i);
406 }
407 }
408
409 // ---- FastLED-specific methods (matching flat_map API) ----
410
411 Value get(const Key& key, const Value& defaultValue) const FL_NOEXCEPT {
412 size_type idx = find_index(key);
413 return idx != npos() ? mData[idx].second : defaultValue;
414 }
415
416 bool get(const Key& key, Value* out_value) const FL_NOEXCEPT {
417 size_type idx = find_index(key);
418 if (idx != npos()) {
419 *out_value = mData[idx].second;
420 return true;
421 }
422 return false;
423 }
424
426 size_type idx = find_index(key);
427 if (idx != npos()) {
428 mData[idx].second = value;
429 return fl::pair<iterator, bool>(iterator(this, idx), false);
430 }
431 idx = do_insert(value_type(key, value));
432 return fl::pair<iterator, bool>(iterator(this, idx), true);
433 }
434
435 bool update(const Key& key, const Value& value) FL_NOEXCEPT {
436 size_type idx = find_index(key);
437 if (idx != npos()) {
438 mData[idx].second = value;
439 return true;
440 }
442 return true;
443 }
444
445 bool update(const Key& key, Value&& value) FL_NOEXCEPT {
446 size_type idx = find_index(key);
447 if (idx != npos()) {
448 mData[idx].second = fl::move(value);
449 return true;
450 }
452 return true;
453 }
454
455 // Comparison (order-independent)
456 bool operator==(const unordered_map_small& other) const FL_NOEXCEPT {
457 if (mSize != other.mSize) return false;
458 for (size_type i = 0; i < mData.size(); ++i) {
459 if (!mOccupied.test(i)) continue;
460 size_type oidx = other.find_index(mData[i].first);
461 if (oidx == npos() || !(other.mData[oidx].second == mData[i].second)) {
462 return false;
463 }
464 }
465 return true;
466 }
467
468 bool operator!=(const unordered_map_small& other) const FL_NOEXCEPT {
469 return !(*this == other);
470 }
471};
472
473template <typename Key, typename Value, typename Equal>
478
479} // namespace fl
uint8_t pos
Definition Blur.ino:11
A dynamic bitset implementation that can be resized at runtime.
Polymorphic memory resource base class (PMR-style).
bool insert(Key &&key, Value &&value, insert_result *result=nullptr) FL_NOEXCEPT
const Value & at(const Key &key) const FL_NOEXCEPT
unordered_map_small(unordered_map_small &&other) FL_NOEXCEPT
bool contains(const Key &key) const FL_NOEXCEPT
size_type capacity() const FL_NOEXCEPT
fl::pair< iterator, bool > emplace(Args &&... args) FL_NOEXCEPT
bool has(const Key &key) const FL_NOEXCEPT
bool get(const Key &key, Value *out_value) const FL_NOEXCEPT
iterator begin() FL_NOEXCEPT
size_type do_insert(value_type &&kv) FL_NOEXCEPT
iterator erase(iterator pos) FL_NOEXCEPT
bool update(const Key &key, const Value &value) FL_NOEXCEPT
const_iterator find(const Key &key) const FL_NOEXCEPT
size_type count(const Key &key) const FL_NOEXCEPT
size_type do_insert(const value_type &kv) FL_NOEXCEPT
void swap(unordered_map_small &other) FL_NOEXCEPT
const_iterator cend() const FL_NOEXCEPT
unordered_map_small(const unordered_map_small &other) FL_NOEXCEPT
unordered_map_small(const Equal &eq, memory_resource *resource=nullptr) FL_NOEXCEPT
void place_at(size_type idx, value_type &&kv) FL_NOEXCEPT
memory_resource * get_memory_resource() const FL_NOEXCEPT
Value & at(const Key &key) FL_NOEXCEPT
unordered_map_small & operator=(unordered_map_small &&other) FL_NOEXCEPT
unordered_map_small(memory_resource *resource) FL_NOEXCEPT
size_type append(const value_type &kv) FL_NOEXCEPT
void reserve(size_type n) FL_NOEXCEPT
size_type erase(const Key &key) FL_NOEXCEPT
const_iterator end() const FL_NOEXCEPT
bool empty() const FL_NOEXCEPT
iterator erase(const_iterator pos) FL_NOEXCEPT
Value & operator[](const Key &key) FL_NOEXCEPT
bool update(const Key &key, Value &&value) FL_NOEXCEPT
fl::pair< iterator, bool > insert(value_type &&kv) FL_NOEXCEPT
static size_type npos() FL_NOEXCEPT
size_type max_size() const FL_NOEXCEPT
fl::pair< iterator, bool > insert(const value_type &kv) FL_NOEXCEPT
bool operator!=(const unordered_map_small &other) const FL_NOEXCEPT
bool operator==(const unordered_map_small &other) const FL_NOEXCEPT
iterator find(const Key &key) FL_NOEXCEPT
fl::pair< iterator, bool > insert_or_update(const Key &key, const Value &value) FL_NOEXCEPT
fl::pair< Key, Value > value_type
const_iterator cbegin() const FL_NOEXCEPT
fl::vector< value_type > vector_type
void place_at(size_type idx, const value_type &kv) FL_NOEXCEPT
iterator end() FL_NOEXCEPT
key_equal key_eq() const FL_NOEXCEPT
unordered_map_small & operator=(const unordered_map_small &other) FL_NOEXCEPT
size_type size() const FL_NOEXCEPT
size_type find_free_slot() const FL_NOEXCEPT
Value get(const Key &key, const Value &defaultValue) const FL_NOEXCEPT
const_iterator begin() const FL_NOEXCEPT
size_type find_index(const Key &key) const FL_NOEXCEPT
size_type append(value_type &&kv) FL_NOEXCEPT
bool insert(const Key &key, const Value &value, insert_result *result=nullptr) 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
void swap(T &a, T &b) FL_NOEXCEPT
Definition s16x16x4.h:877
constexpr int type_rank< T >::value
int read()
MapRedBlackTree< Key, T, Compare, fl::allocator_slab< char > > map
Definition map.h:283
memory_resource * default_memory_resource() FL_NOEXCEPT
Get the default memory resource (wraps fl::Malloc / fl::Free / fl::realloc).
void swap(array< T, N > &lhs, array< T, N > &rhs) FL_NOEXCEPT
Definition array.h:209
void free(void *ptr)
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
Base definition for an LED controller.
Definition crgb.hpp:179
corkscrew_args args
Definition old.h:149
#define FL_NOEXCEPT
Definition Keyboard.h:22
bool operator()(const T &a, const T &b) const FL_NOEXCEPT
reference operator*() const FL_NOEXCEPT
const_iterator operator++(int) FL_NOEXCEPT
const_iterator(const unordered_map_small *map, size_type idx) FL_NOEXCEPT
bool operator==(const const_iterator &o) const FL_NOEXCEPT
const_iterator & operator++() FL_NOEXCEPT
bool operator!=(const const_iterator &o) const FL_NOEXCEPT
const_iterator(const iterator &it) FL_NOEXCEPT
bool operator==(const iterator &o) const FL_NOEXCEPT
iterator & operator++() FL_NOEXCEPT
bool operator!=(const iterator &o) const FL_NOEXCEPT
iterator(unordered_map_small *map, size_type idx) FL_NOEXCEPT
reference operator*() const FL_NOEXCEPT
fl::forward_iterator_tag iterator_category
pointer operator->() const FL_NOEXCEPT
iterator operator++(int) FL_NOEXCEPT