C/C++ API Reference
Loading...
Searching...
No Matches
dynamic_hash_map.h
1// Copyright 2026 The Pigweed Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not
4// use this file except in compliance with the License. You may obtain a copy of
5// the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations under
13// the License.
14#pragma once
15
16#include <cstddef>
17#include <cstdint>
18#include <functional>
19#include <initializer_list>
20#include <iterator>
21#include <limits>
22#include <optional>
23#include <type_traits>
24#include <utility>
25
26#include "pw_allocator/allocator.h"
27#include "pw_assert/assert.h"
28#include "pw_containers/dynamic_deque.h"
29#include "pw_containers/dynamic_ptr_vector.h"
30#include "pw_containers/functional.h"
31#include "pw_preprocessor/compiler.h"
32
33namespace pw {
34
36
57template <typename Key,
58 typename Value,
59 typename Hash = pw::Hash,
60 typename Equal = pw::EqualTo,
61 typename SizeType = uint16_t>
63 private:
66 struct Node {
68 std::pair<const Key, Value> key_value;
69
71 Node* next = nullptr;
72
74 SizeType index = 0;
75
76 template <typename K, typename... Args>
77 explicit Node(K&& key, Args&&... args)
78 : key_value(std::piecewise_construct,
79 std::forward_as_tuple(std::forward<K>(key)),
80 std::forward_as_tuple(std::forward<Args>(args)...)) {}
81 };
82
83 using NodeType = Node;
84 using NodeVectorType = pw::DynamicPtrVector<NodeType, SizeType>;
85 using KeyValueType = decltype(std::declval<NodeType>().key_value);
86
87 // Intermediate type for math to prevent overflow while staying efficient.
88 // Uses 32-bit for 16-bit SizeType, and 64-bit for 32-bit SizeType.
89 using CalcType = std::
90 conditional_t<sizeof(SizeType) < sizeof(uint32_t), uint32_t, uint64_t>;
91
95 template <bool kIsConst>
96 class Iterator {
97 public:
98 using iterator_category = std::forward_iterator_tag;
99 using value_type = KeyValueType;
100 using difference_type = std::ptrdiff_t;
101 using reference =
102 std::conditional_t<kIsConst, const value_type&, value_type&>;
103 using pointer =
104 std::conditional_t<kIsConst, const value_type*, value_type*>;
105
106 constexpr Iterator() = default;
107
108 template <bool kOtherConst,
109 typename = std::enable_if_t<kIsConst && !kOtherConst>>
110 constexpr Iterator(const Iterator<kOtherConst>& other) : it_(other.it_) {}
111
112 reference operator*() const { return it_->key_value; }
113 pointer operator->() const { return &(it_->key_value); }
114
115 Iterator operator++() {
116 ++it_;
117 return *this;
118 }
119 Iterator operator++(int) {
120 Iterator tmp = *this;
121 ++it_;
122 return tmp;
123 }
124 friend bool operator==(Iterator lhs, Iterator rhs) {
125 return lhs.it_ == rhs.it_;
126 }
127 friend bool operator!=(Iterator lhs, Iterator rhs) {
128 return lhs.it_ != rhs.it_;
129 }
130
131 private:
132 using BaseIterator =
133 std::conditional_t<kIsConst,
134 typename NodeVectorType::const_iterator,
135 typename NodeVectorType::iterator>;
136
137 constexpr explicit Iterator(BaseIterator it) : it_(it) {}
138
139 friend class DynamicHashMap;
140
141 BaseIterator it_;
142 };
143
144 public:
145 using key_type = Key;
146 using mapped_type = Value;
147 using value_type = KeyValueType;
148 using size_type = SizeType;
149 using difference_type = std::ptrdiff_t;
150 using hasher = Hash;
151 using key_equal = Equal;
152 using allocator_type = Allocator;
153 using reference = value_type&;
154 using const_reference = const value_type&;
155 using pointer = value_type*;
156 using const_pointer = const value_type*;
157 using iterator = Iterator<false>;
158 using const_iterator = Iterator<true>;
159 using node_type = NodeType;
160 using insert_return_type = std::pair<iterator, bool>;
161
166 constexpr explicit DynamicHashMap(Allocator& allocator,
167 const Hash& hash = Hash(),
168 const Equal& equal = Equal()) noexcept
169 : buckets_(allocator), nodes_(allocator), hash_(hash), equal_(equal) {}
170
171 ~DynamicHashMap() { clear(); }
172
176 DynamicHashMap& operator=(const DynamicHashMap&) = delete;
177
181 : buckets_(std::move(other.buckets_)),
182 nodes_(std::move(other.nodes_)),
183 hash_(std::move(other.hash_)),
184 equal_(std::move(other.equal_)),
185 max_load_factor_percent_(other.max_load_factor_percent_),
186 rehash_threshold_(other.rehash_threshold_) {}
187
191 if (&other == this) {
192 return *this;
193 }
194 clear();
195 buckets_ = std::move(other.buckets_);
196 nodes_ = std::move(other.nodes_);
197 hash_ = std::move(other.hash_);
198 equal_ = std::move(other.equal_);
199 max_load_factor_percent_ = other.max_load_factor_percent_;
200 rehash_threshold_ = other.rehash_threshold_;
201 return *this;
202 }
203
205 allocator_type& get_allocator() const { return nodes_.get_allocator(); }
206
207 // Iterators
208
209 iterator begin() { return iterator(nodes_.begin()); }
210 const_iterator begin() const { return const_iterator(nodes_.begin()); }
211 const_iterator cbegin() const { return begin(); }
212 iterator end() { return iterator(nodes_.end()); }
213 const_iterator end() const { return const_iterator(nodes_.end()); }
214 const_iterator cend() const { return end(); }
215
216 // Capacity
217
218 [[nodiscard]] bool empty() const { return nodes_.empty(); }
219 size_type size() const { return nodes_.size(); }
220 size_type max_size() const { return nodes_.max_size(); }
221
222 // Modifiers
223
227 void clear() {
228 nodes_.clear();
229 buckets_.clear();
230 }
231
235 [[nodiscard]] std::optional<insert_return_type> try_insert(
236 const value_type& value) {
237 return try_emplace(value.first, value.second);
238 }
239
240 // Moving into a fallible insertion is deleted to prevent "ghost moves."
241 // If allocation fails, the object would be moved-from but not stored.
242 // Use try_emplace instead to ensure moves only occur on success.
243 std::optional<insert_return_type> try_insert(value_type&&) = delete;
244
246 insert_return_type insert(const value_type& value) {
247 return emplace(value.first, value.second);
248 }
249
250 insert_return_type insert(value_type&& value) {
251 return emplace(std::move(value.first), std::move(value.second));
252 }
253
254 template <typename InputIt,
255 typename = containers::internal::EnableIfInputIterator<InputIt>>
256 void insert(InputIt first, InputIt last) {
257 for (auto it = first; it != last; ++it) {
258 emplace(it->first, it->second);
259 }
260 }
261
262 void insert(std::initializer_list<value_type> ilist) {
263 insert(ilist.begin(), ilist.end());
264 }
265
269 template <typename K, typename... Args>
270 [[nodiscard]] std::optional<std::pair<iterator, bool>> try_emplace(
271 K&& key, Args&&... args) {
272 return TryEmplaceImpl(std::forward<K>(key), std::forward<Args>(args)...);
273 }
274
276 template <typename K, typename... Args>
277 std::pair<iterator, bool> emplace(K&& key, Args&&... args) {
278 auto result =
279 try_emplace(std::forward<K>(key), std::forward<Args>(args)...);
280 PW_ASSERT(result.has_value());
281 return result.value();
282 }
283
291 iterator erase(iterator pos) {
292 PW_ASSERT(pos != end());
293 NodeType* node = &(*pos.it_);
294 UnlinkFromBucket(node);
295 return EraseFromVector(node->index);
296 }
297
298 iterator erase(const_iterator pos) {
299 auto it = iterator(nodes_.begin() + pos.it_->index);
300 return erase(it);
301 }
302
307 iterator erase(const_iterator first, const_iterator last) {
308 if (first == last) {
309 return (first == end()) ? end()
310 : iterator(nodes_.begin() + first.it_->index);
311 }
312
313 size_type index_first = first.it_->index;
314 size_type index_last = (last == end()) ? nodes_.size() : last.it_->index;
315 for (size_type i = index_last - 1; i > index_first; --i) {
316 auto it = iterator(nodes_.begin() + i);
317 erase(it);
318 }
319
320 return erase(first);
321 }
322
325 template <typename K>
326 size_type erase(K&& key) {
327 NodeType* node = UnlinkFromBucket(std::forward<K>(key));
328 if (node == nullptr) {
329 return 0;
330 }
331 EraseFromVector(node->index);
332 return 1;
333 }
334
336 void swap(DynamicHashMap& other) noexcept {
337 buckets_.swap(other.buckets_);
338 nodes_.swap(other.nodes_);
339 std::swap(hash_, other.hash_);
340 std::swap(equal_, other.equal_);
341 std::swap(max_load_factor_percent_, other.max_load_factor_percent_);
342 std::swap(rehash_threshold_, other.rehash_threshold_);
343 }
344
355 void merge(DynamicHashMap& other) {
356 if (&other == this) {
357 return;
358 }
359
360 auto it = other.begin();
361 while (it != other.end()) {
362 auto result = try_emplace(it->first, std::move(it->second));
363 if (result.has_value() && result.value().second) {
364 it = other.erase(it);
365 } else {
366 ++it;
367 }
368 }
369 }
370
371 void merge(DynamicHashMap&& other) { merge(other); }
372
373 // Lookup
374
379 mapped_type& at(const key_type& key) {
380 auto it = find(key);
381 PW_ASSERT(it != end());
382 return it->second;
383 }
384
385 const mapped_type& at(const key_type& key) const {
386 auto it = find(key);
387 PW_ASSERT(it != end());
388 return it->second;
389 }
390
395 template <typename U = mapped_type,
396 typename = std::enable_if_t<std::is_default_constructible_v<U>>>
397 mapped_type& operator[](const key_type& key) {
398 return emplace(key).first->second;
399 }
400
402 size_type count(const key_type& key) const {
403 return find(key) != end() ? 1 : 0;
404 }
405
409 iterator find(const key_type& key) {
410 NodeType* node = FindImpl(key);
411 if (node != nullptr) {
412 return iterator(nodes_.begin() + node->index);
413 }
414 return end();
415 }
416
417 const_iterator find(const key_type& key) const {
418 NodeType* node = FindImpl(key);
419 if (node != nullptr) {
420 return const_iterator(nodes_.begin() + node->index);
421 }
422 return cend();
423 }
424
427 bool contains(const key_type& key) const { return find(key) != end(); }
428
432 std::pair<iterator, iterator> equal_range(const key_type& key) {
433 auto it = find(key);
434 if (it == end()) {
435 return std::make_pair(it, it);
436 }
437 return std::make_pair(it, std::next(it));
438 }
439
440 std::pair<const_iterator, const_iterator> equal_range(
441 const key_type& key) const {
442 auto it = find(key);
443 if (it == end()) {
444 return std::make_pair(it, it);
445 }
446 return std::make_pair(it, std::next(it));
447 }
448
449 // Hash policy
450
453 size_type load_factor_percent() const {
454 if (buckets_.empty()) {
455 return 0;
456 }
457 return (nodes_.size() * 100) / buckets_.size();
458 }
459
462 size_type max_load_factor_percent() const { return max_load_factor_percent_; }
463
468 void max_load_factor_percent(size_type percent) {
469 PW_ASSERT(percent > 0 && percent <= kMaxLoadFactorPercentLimit);
470 max_load_factor_percent_ = percent;
471 UpdateRehashThreshold();
472 }
473
478 [[nodiscard]] bool try_rehash(size_type count) {
479 if (count < buckets_.size()) {
480 return true;
481 }
482
484 if (!new_buckets.try_assign(count, nullptr)) {
485 return false;
486 }
487
488 for (auto& node : nodes_) {
489 size_type bucket_index = hash_(node.key_value.first) % new_buckets.size();
490 node.next = new_buckets[bucket_index];
491 new_buckets[bucket_index] = &node;
492 }
493
494 buckets_ = std::move(new_buckets);
495
496 UpdateRehashThreshold();
497 return true;
498 }
499
502 void rehash(size_type count) { PW_ASSERT(try_rehash(count)); }
503
515 [[nodiscard]] bool try_reserve(size_type count) {
516 const CalcType required_buckets_calc =
517 (static_cast<CalcType>(count) * 100 + max_load_factor_percent() - 1) /
519 if (required_buckets_calc > std::numeric_limits<size_type>::max()) {
520 return false;
521 }
522
523 size_type required_buckets = static_cast<size_type>(required_buckets_calc);
524 if (!try_rehash(required_buckets)) {
525 return false;
526 }
527 if (!nodes_.try_reserve_ptr(count)) {
528 return false;
529 }
530
531 return true;
532 }
533
536 void reserve(size_type count) { PW_ASSERT(try_reserve(count)); }
537
538 private:
539 size_type GetBucketIndex(const key_type& key) const {
540 PW_ASSERT(!buckets_.empty());
541 return hash_(key) % buckets_.size();
542 }
543
544 size_type CalculateNewBucketCount(size_type current_count) const {
545 size_type max_bucket_count = buckets_.max_size();
546 if (current_count > max_bucket_count / 2) {
547 return max_bucket_count;
548 }
549 return current_count * 2;
550 }
551
552 void UpdateRehashThreshold() {
553 if (buckets_.empty()) {
554 rehash_threshold_ = 0;
555 } else {
556 rehash_threshold_ = static_cast<size_type>(
557 (static_cast<CalcType>(buckets_.size()) * max_load_factor_percent()) /
558 100);
559 }
560 }
561
562 NodeType* FindImpl(const key_type& key) const {
563 if (empty()) {
564 return nullptr;
565 }
566 size_type bucket_index = GetBucketIndex(key);
567 NodeType* node = buckets_[bucket_index];
568 while (node != nullptr) {
569 if (equal_(node->key_value.first, key)) {
570 return node;
571 }
572 node = node->next;
573 }
574 return nullptr;
575 }
576
577 template <typename K, typename... Args>
578 [[nodiscard]] std::optional<std::pair<iterator, bool>> TryEmplaceImpl(
579 K&& key, Args&&... args) {
580 if (auto it = find(key); it != end()) {
581 return std::make_pair(iterator(it), false);
582 }
583
584 if (size() >= max_size()) {
585 return std::nullopt;
586 }
587
588 if (buckets_.empty()) {
589 if (!buckets_.try_assign(kDefaultInitialBuckets, nullptr)) {
590 return std::nullopt;
591 }
592 UpdateRehashThreshold();
593 }
594
595 if (!nodes_.try_emplace_back(std::forward<K>(key),
596 std::forward<Args>(args)...)) {
597 return std::nullopt;
598 }
599
600 NodeType& new_node = nodes_.back();
601 size_type bucket_index = GetBucketIndex(new_node.key_value.first);
602 new_node.next = buckets_[bucket_index];
603 new_node.index = nodes_.size() - 1;
604 buckets_[bucket_index] = &new_node;
605
606 if (size() > rehash_threshold_) {
607 size_type new_bucket_count = CalculateNewBucketCount(buckets_.size());
608 (void)try_rehash(new_bucket_count);
609 }
610
611 return std::make_pair(iterator(std::prev(nodes_.end())), true);
612 }
613
614 iterator EraseFromVector(size_type index_to_remove) {
615 size_type last_index = nodes_.size() - 1;
616
617 if (index_to_remove != last_index) {
618 nodes_.swap(index_to_remove, last_index);
619 nodes_[index_to_remove].index = index_to_remove;
620 }
621
622 nodes_.pop_back();
623 return iterator(nodes_.begin() + index_to_remove);
624 }
625
626 void UnlinkFromBucket(NodeType* node) {
627 size_type bucket_index = GetBucketIndex(node->key_value.first);
628 FindAndUnlinkFromBucket(bucket_index,
629 [node](NodeType* curr) { return curr == node; });
630 }
631
632 template <typename K>
633 NodeType* UnlinkFromBucket(K&& key) {
634 size_type bucket_index = GetBucketIndex(key);
635 return FindAndUnlinkFromBucket(bucket_index, [this, &key](NodeType* curr) {
636 return equal_(curr->key_value.first, key);
637 });
638 }
639
640 template <typename Predicate>
641 NodeType* FindAndUnlinkFromBucket(size_type bucket_index, Predicate pred) {
642 NodeType* curr = buckets_[bucket_index];
643 NodeType* prev = nullptr;
644
645 while (curr != nullptr) {
646 if (pred(curr)) {
647 if (prev == nullptr) {
648 buckets_[bucket_index] = curr->next;
649 } else {
650 prev->next = curr->next;
651 }
652 return curr;
653 }
654 prev = curr;
655 curr = curr->next;
656 }
657
658 return nullptr;
659 }
660
662 NodeVectorType nodes_;
665 size_type max_load_factor_percent_ = 75;
666 size_type rehash_threshold_ = 0;
667 static constexpr size_type kDefaultInitialBuckets = 11;
668
675 static constexpr size_type kMaxLoadFactorPercentLimit = 500;
676};
677
678} // namespace pw
Definition: allocator.h:45
Definition: dynamic_deque.h:60
bool try_assign(size_type count, const value_type &value)
Definition: generic_deque.h:626
Definition: dynamic_hash_map.h:62
Definition: dynamic_ptr_vector.h:39
constexpr size_type size() const noexcept
Returns the number of elements in the deque.
Definition: generic_deque.h:69
mapped_type & at(const key_type &key)
Definition: dynamic_hash_map.h:379
void rehash(size_type count)
Definition: dynamic_hash_map.h:502
DynamicHashMap(DynamicHashMap &&other) noexcept
Definition: dynamic_hash_map.h:180
insert_return_type insert(const value_type &value)
Inserts a value into the map. Crashes on allocation failure.
Definition: dynamic_hash_map.h:246
std::pair< iterator, bool > emplace(K &&key, Args &&... args)
Constructs an element in-place. Crashes on allocation failure.
Definition: dynamic_hash_map.h:277
void reserve(size_type count)
Definition: dynamic_hash_map.h:536
DynamicHashMap(const DynamicHashMap &)=delete
mapped_type & operator[](const key_type &key)
Definition: dynamic_hash_map.h:397
allocator_type & get_allocator() const
Returns the allocator used by this map.
Definition: dynamic_hash_map.h:205
constexpr DynamicHashMap(Allocator &allocator, const Hash &hash=Hash(), const Equal &equal=Equal()) noexcept
Definition: dynamic_hash_map.h:166
void swap(DynamicHashMap &other) noexcept
Swaps the contents of two maps. No allocations occur.
Definition: dynamic_hash_map.h:336
void max_load_factor_percent(size_type percent)
Definition: dynamic_hash_map.h:468
std::optional< insert_return_type > try_insert(const value_type &value)
Definition: dynamic_hash_map.h:235
void merge(DynamicHashMap &other)
Definition: dynamic_hash_map.h:355
Node * next
Pointer to the next node in the same hash bucket (collision chain).
Definition: dynamic_hash_map.h:71
size_type count(const key_type &key) const
Returns the number of elements with key key (0 or 1).
Definition: dynamic_hash_map.h:402
size_type erase(K &&key)
Definition: dynamic_hash_map.h:326
bool try_reserve(size_type count)
Definition: dynamic_hash_map.h:515
std::pair< const Key, Value > key_value
The stored key-value pair.
Definition: dynamic_hash_map.h:68
std::optional< std::pair< iterator, bool > > try_emplace(K &&key, Args &&... args)
Definition: dynamic_hash_map.h:270
iterator find(const key_type &key)
Definition: dynamic_hash_map.h:409
bool contains(const key_type &key) const
Definition: dynamic_hash_map.h:427
size_type max_load_factor_percent() const
Definition: dynamic_hash_map.h:462
iterator erase(iterator pos)
Definition: dynamic_hash_map.h:291
DynamicHashMap & operator=(DynamicHashMap &&other) noexcept
Definition: dynamic_hash_map.h:190
SizeType index
Index of this node in the nodes_ vector, enabling O(1) erasure.
Definition: dynamic_hash_map.h:74
iterator erase(const_iterator first, const_iterator last)
Definition: dynamic_hash_map.h:307
bool try_rehash(size_type count)
Definition: dynamic_hash_map.h:478
void clear()
Definition: dynamic_hash_map.h:227
size_type load_factor_percent() const
Definition: dynamic_hash_map.h:453
std::pair< iterator, iterator > equal_range(const key_type &key)
Definition: dynamic_hash_map.h:432
void swap(DynamicDeque &other) noexcept
Swaps the contents of two deques. No allocations occur.
Definition: dynamic_deque.h:182
bool Equal(const C1 &c1, const C2 &c2)
Definition: algorithm.h:248
bool try_emplace_back(Args &&... args)
Definition: dynamic_ptr_vector.h:294
void swap(DynamicPtrVector &other)
Swaps the contents of this DynamicPtrVector with another one.
Definition: dynamic_ptr_vector.h:394
bool try_reserve_ptr(size_type new_capacity)
Definition: dynamic_ptr_vector.h:201
Status Hash(ConstByteSpan message, ByteSpan out_digest)
Definition: sha256.h:167
#define PW_NO_UNIQUE_ADDRESS
Definition: compiler.h:297
The Pigweed namespace.
Definition: alignment.h:27
Definition: functional.h:228
Definition: functional.h:217