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
67template <typename Key,
68 typename Value,
69 typename Hash = pw::Hash,
70 typename Equal = pw::EqualTo,
71 typename SizeType = uint16_t>
73 private:
76 struct Node {
78 std::pair<const Key, Value> key_value;
79
81 Node* next = nullptr;
82
84 SizeType index = 0;
85
86 template <typename K, typename... Args>
87 explicit Node(K&& key, Args&&... args)
88 : key_value(std::piecewise_construct,
89 std::forward_as_tuple(std::forward<K>(key)),
90 std::forward_as_tuple(std::forward<Args>(args)...)) {}
91 };
92
93 using NodeType = Node;
94 using NodeVectorType = pw::DynamicPtrVector<NodeType, SizeType>;
95 using KeyValueType = decltype(std::declval<NodeType>().key_value);
96
97 // Intermediate type for math to prevent overflow while staying efficient.
98 // Uses 32-bit for 16-bit SizeType, and 64-bit for 32-bit SizeType.
99 using CalcType = std::
100 conditional_t<sizeof(SizeType) < sizeof(uint32_t), uint32_t, uint64_t>;
101
105 template <bool kIsConst>
106 class Iterator {
107 public:
108 using iterator_category = std::forward_iterator_tag;
109 using value_type = KeyValueType;
110 using difference_type = std::ptrdiff_t;
111 using reference =
112 std::conditional_t<kIsConst, const value_type&, value_type&>;
113 using pointer =
114 std::conditional_t<kIsConst, const value_type*, value_type*>;
115
116 constexpr Iterator() = default;
117
118 template <bool kOtherConst,
119 typename = std::enable_if_t<kIsConst && !kOtherConst>>
120 constexpr Iterator(const Iterator<kOtherConst>& other) : it_(other.it_) {}
121
122 reference operator*() const { return it_->key_value; }
123 pointer operator->() const { return &(it_->key_value); }
124
125 Iterator operator++() {
126 ++it_;
127 return *this;
128 }
129 Iterator operator++(int) {
130 Iterator tmp = *this;
131 ++it_;
132 return tmp;
133 }
134 friend bool operator==(Iterator lhs, Iterator rhs) {
135 return lhs.it_ == rhs.it_;
136 }
137 friend bool operator!=(Iterator lhs, Iterator rhs) {
138 return lhs.it_ != rhs.it_;
139 }
140
141 private:
142 using BaseIterator =
143 std::conditional_t<kIsConst,
144 typename NodeVectorType::const_iterator,
145 typename NodeVectorType::iterator>;
146
147 constexpr explicit Iterator(BaseIterator it) : it_(it) {}
148
149 friend class DynamicHashMap;
150
151 BaseIterator it_;
152 };
153
154 public:
155 using key_type = Key;
156 using mapped_type = Value;
157 using value_type = KeyValueType;
158 using size_type = SizeType;
159 using difference_type = std::ptrdiff_t;
160 using hasher = Hash;
161 using key_equal = Equal;
162 using allocator_type = Allocator;
163 using reference = value_type&;
164 using const_reference = const value_type&;
165 using pointer = value_type*;
166 using const_pointer = const value_type*;
167 using iterator = Iterator<false>;
168 using const_iterator = Iterator<true>;
169 using node_type = NodeType;
170 using insert_return_type = std::pair<iterator, bool>;
171
176 constexpr explicit DynamicHashMap(Allocator& allocator,
177 const Hash& hash = Hash(),
178 const Equal& equal = Equal()) noexcept
179 : buckets_(allocator), nodes_(allocator), hash_(hash), equal_(equal) {}
180
181 ~DynamicHashMap() { clear(); }
182
186 DynamicHashMap& operator=(const DynamicHashMap&) = delete;
187
191 : buckets_(std::move(other.buckets_)),
192 nodes_(std::move(other.nodes_)),
193 hash_(std::move(other.hash_)),
194 equal_(std::move(other.equal_)),
195 max_load_factor_percent_(other.max_load_factor_percent_),
196 rehash_threshold_(other.rehash_threshold_) {}
197
201 if (&other == this) {
202 return *this;
203 }
204 clear();
205 buckets_ = std::move(other.buckets_);
206 nodes_ = std::move(other.nodes_);
207 hash_ = std::move(other.hash_);
208 equal_ = std::move(other.equal_);
209 max_load_factor_percent_ = other.max_load_factor_percent_;
210 rehash_threshold_ = other.rehash_threshold_;
211 return *this;
212 }
213
215 allocator_type& get_allocator() const { return nodes_.get_allocator(); }
216
217 // Iterators
218
219 iterator begin() { return iterator(nodes_.begin()); }
220 const_iterator begin() const { return const_iterator(nodes_.begin()); }
221 const_iterator cbegin() const { return begin(); }
222 iterator end() { return iterator(nodes_.end()); }
223 const_iterator end() const { return const_iterator(nodes_.end()); }
224 const_iterator cend() const { return end(); }
225
226 // Capacity
227
228 [[nodiscard]] bool empty() const { return nodes_.empty(); }
229 size_type size() const { return nodes_.size(); }
230 size_type max_size() const { return nodes_.max_size(); }
231
232 // Modifiers
233
237 void clear() {
238 nodes_.clear();
239 buckets_.clear();
240 }
241
243 void reset() {
244 nodes_.reset();
245 buckets_.reset();
246 }
247
251 [[nodiscard]] std::optional<insert_return_type> try_insert(
252 const value_type& value) {
253 return try_emplace(value.first, value.second);
254 }
255
256 // Moving into a fallible insertion is deleted to prevent "ghost moves."
257 // If allocation fails, the object would be moved-from but not stored.
258 // Use try_emplace instead to ensure moves only occur on success.
259 std::optional<insert_return_type> try_insert(value_type&&) = delete;
260
262 insert_return_type insert(const value_type& value) {
263 return emplace(value.first, value.second);
264 }
265
266 insert_return_type insert(value_type&& value) {
267 return emplace(std::move(value.first), std::move(value.second));
268 }
269
270 template <typename InputIt,
271 typename = containers::internal::EnableIfInputIterator<InputIt>>
272 void insert(InputIt first, InputIt last) {
273 for (auto it = first; it != last; ++it) {
274 emplace(it->first, it->second);
275 }
276 }
277
278 void insert(std::initializer_list<value_type> ilist) {
279 insert(ilist.begin(), ilist.end());
280 }
281
285 template <typename K, typename... Args>
286 [[nodiscard]] std::optional<std::pair<iterator, bool>> try_emplace(
287 K&& key, Args&&... args) {
288 return TryEmplaceImpl(std::forward<K>(key), std::forward<Args>(args)...);
289 }
290
292 template <typename K, typename... Args>
293 std::pair<iterator, bool> emplace(K&& key, Args&&... args) {
294 auto result =
295 try_emplace(std::forward<K>(key), std::forward<Args>(args)...);
296 PW_ASSERT(result.has_value());
297 return result.value();
298 }
299
307 iterator erase(iterator pos) {
308 PW_ASSERT(pos != end());
309 NodeType* node = &(*pos.it_);
310 UnlinkFromBucket(node);
311 return EraseFromVector(node->index);
312 }
313
314 iterator erase(const_iterator pos) {
315 auto it = iterator(nodes_.begin() + pos.it_->index);
316 return erase(it);
317 }
318
323 iterator erase(const_iterator first, const_iterator last) {
324 if (first == last) {
325 return (first == end()) ? end()
326 : iterator(nodes_.begin() + first.it_->index);
327 }
328
329 size_type index_first = first.it_->index;
330 size_type index_last = (last == end()) ? nodes_.size() : last.it_->index;
331 for (size_type i = index_last - 1; i > index_first; --i) {
332 auto it = iterator(nodes_.begin() + i);
333 erase(it);
334 }
335
336 return erase(first);
337 }
338
341 template <typename K>
342 size_type erase(K&& key) {
343 NodeType* node = UnlinkFromBucket(std::forward<K>(key));
344 if (node == nullptr) {
345 return 0;
346 }
347 EraseFromVector(node->index);
348 return 1;
349 }
350
352 void swap(DynamicHashMap& other) noexcept {
353 buckets_.swap(other.buckets_);
354 nodes_.swap(other.nodes_);
355 std::swap(hash_, other.hash_);
356 std::swap(equal_, other.equal_);
357 std::swap(max_load_factor_percent_, other.max_load_factor_percent_);
358 std::swap(rehash_threshold_, other.rehash_threshold_);
359 }
360
371 void merge(DynamicHashMap& other) {
372 if (&other == this) {
373 return;
374 }
375
376 auto it = other.begin();
377 while (it != other.end()) {
378 auto result = try_emplace(it->first, std::move(it->second));
379 if (result.has_value() && result.value().second) {
380 it = other.erase(it);
381 } else {
382 ++it;
383 }
384 }
385 }
386
387 void merge(DynamicHashMap&& other) { merge(other); }
388
389 // Lookup
390
395 mapped_type& at(const key_type& key) {
396 auto it = find(key);
397 PW_ASSERT(it != end());
398 return it->second;
399 }
400
401 const mapped_type& at(const key_type& key) const {
402 auto it = find(key);
403 PW_ASSERT(it != end());
404 return it->second;
405 }
406
411 template <typename U = mapped_type,
412 typename = std::enable_if_t<std::is_default_constructible_v<U>>>
413 mapped_type& operator[](const key_type& key) {
414 return emplace(key).first->second;
415 }
416
418 size_type count(const key_type& key) const {
419 return find(key) != end() ? 1 : 0;
420 }
421
425 iterator find(const key_type& key) {
426 NodeType* node = FindImpl(key);
427 if (node != nullptr) {
428 return iterator(nodes_.begin() + node->index);
429 }
430 return end();
431 }
432
433 const_iterator find(const key_type& key) const {
434 NodeType* node = FindImpl(key);
435 if (node != nullptr) {
436 return const_iterator(nodes_.begin() + node->index);
437 }
438 return cend();
439 }
440
443 bool contains(const key_type& key) const { return find(key) != end(); }
444
448 std::pair<iterator, iterator> equal_range(const key_type& key) {
449 auto it = find(key);
450 if (it == end()) {
451 return std::make_pair(it, it);
452 }
453 return std::make_pair(it, std::next(it));
454 }
455
456 std::pair<const_iterator, const_iterator> equal_range(
457 const key_type& key) const {
458 auto it = find(key);
459 if (it == end()) {
460 return std::make_pair(it, it);
461 }
462 return std::make_pair(it, std::next(it));
463 }
464
465 // Hash policy
466
469 size_type load_factor_percent() const {
470 if (buckets_.empty()) {
471 return 0;
472 }
473 return (nodes_.size() * 100) / buckets_.size();
474 }
475
478 size_type max_load_factor_percent() const { return max_load_factor_percent_; }
479
484 void max_load_factor_percent(size_type percent) {
485 PW_ASSERT(percent > 0 && percent <= kMaxLoadFactorPercentLimit);
486 max_load_factor_percent_ = percent;
487 UpdateRehashThreshold();
488 }
489
494 [[nodiscard]] bool try_rehash(size_type count) {
495 if (count < buckets_.size()) {
496 return true;
497 }
498
500 if (!new_buckets.try_assign(count, nullptr)) {
501 return false;
502 }
503
504 for (auto& node : nodes_) {
505 size_type bucket_index = hash_(node.key_value.first) % new_buckets.size();
506 node.next = new_buckets[bucket_index];
507 new_buckets[bucket_index] = &node;
508 }
509
510 buckets_ = std::move(new_buckets);
511
512 UpdateRehashThreshold();
513 return true;
514 }
515
518 void rehash(size_type count) { PW_ASSERT(try_rehash(count)); }
519
531 [[nodiscard]] bool try_reserve(size_type count) {
532 const CalcType required_buckets_calc =
533 (static_cast<CalcType>(count) * 100 + max_load_factor_percent() - 1) /
535 if (required_buckets_calc > std::numeric_limits<size_type>::max()) {
536 return false;
537 }
538
539 size_type required_buckets = static_cast<size_type>(required_buckets_calc);
540 if (!try_rehash(required_buckets)) {
541 return false;
542 }
543 if (!nodes_.try_reserve_ptr(count)) {
544 return false;
545 }
546
547 return true;
548 }
549
552 void reserve(size_type count) { PW_ASSERT(try_reserve(count)); }
553
554 private:
555 size_type GetBucketIndex(const key_type& key) const {
556 PW_ASSERT(!buckets_.empty());
557 return hash_(key) % buckets_.size();
558 }
559
560 size_type CalculateNewBucketCount(size_type current_count) const {
561 size_type max_bucket_count = buckets_.max_size();
562 if (current_count > max_bucket_count / 2) {
563 return max_bucket_count;
564 }
565 return current_count * 2;
566 }
567
568 void UpdateRehashThreshold() {
569 if (buckets_.empty()) {
570 rehash_threshold_ = 0;
571 } else {
572 rehash_threshold_ = static_cast<size_type>(
573 (static_cast<CalcType>(buckets_.size()) * max_load_factor_percent()) /
574 100);
575 }
576 }
577
578 NodeType* FindImpl(const key_type& key) const {
579 if (empty()) {
580 return nullptr;
581 }
582 size_type bucket_index = GetBucketIndex(key);
583 NodeType* node = buckets_[bucket_index];
584 while (node != nullptr) {
585 if (equal_(node->key_value.first, key)) {
586 return node;
587 }
588 node = node->next;
589 }
590 return nullptr;
591 }
592
593 template <typename K, typename... Args>
594 [[nodiscard]] std::optional<std::pair<iterator, bool>> TryEmplaceImpl(
595 K&& key, Args&&... args) {
596 if (auto it = find(key); it != end()) {
597 return std::make_pair(iterator(it), false);
598 }
599
600 if (size() >= max_size()) {
601 return std::nullopt;
602 }
603
604 if (buckets_.empty()) {
605 if (!buckets_.try_assign(kDefaultInitialBuckets, nullptr)) {
606 return std::nullopt;
607 }
608 UpdateRehashThreshold();
609 }
610
611 if (!nodes_.try_emplace_back(std::forward<K>(key),
612 std::forward<Args>(args)...)) {
613 return std::nullopt;
614 }
615
616 NodeType& new_node = nodes_.back();
617 size_type bucket_index = GetBucketIndex(new_node.key_value.first);
618 new_node.next = buckets_[bucket_index];
619 new_node.index = nodes_.size() - 1;
620 buckets_[bucket_index] = &new_node;
621
622 if (size() > rehash_threshold_) {
623 size_type new_bucket_count = CalculateNewBucketCount(buckets_.size());
624 (void)try_rehash(new_bucket_count);
625 }
626
627 return std::make_pair(iterator(std::prev(nodes_.end())), true);
628 }
629
630 iterator EraseFromVector(size_type index_to_remove) {
631 size_type last_index = nodes_.size() - 1;
632
633 if (index_to_remove != last_index) {
634 nodes_.swap(index_to_remove, last_index);
635 nodes_[index_to_remove].index = index_to_remove;
636 }
637
638 nodes_.pop_back();
639 return iterator(nodes_.begin() + index_to_remove);
640 }
641
642 void UnlinkFromBucket(NodeType* node) {
643 size_type bucket_index = GetBucketIndex(node->key_value.first);
644 FindAndUnlinkFromBucket(bucket_index,
645 [node](NodeType* curr) { return curr == node; });
646 }
647
648 template <typename K>
649 NodeType* UnlinkFromBucket(K&& key) {
650 size_type bucket_index = GetBucketIndex(key);
651 return FindAndUnlinkFromBucket(bucket_index, [this, &key](NodeType* curr) {
652 return equal_(curr->key_value.first, key);
653 });
654 }
655
656 template <typename Predicate>
657 NodeType* FindAndUnlinkFromBucket(size_type bucket_index, Predicate pred) {
658 NodeType* curr = buckets_[bucket_index];
659 NodeType* prev = nullptr;
660
661 while (curr != nullptr) {
662 if (pred(curr)) {
663 if (prev == nullptr) {
664 buckets_[bucket_index] = curr->next;
665 } else {
666 prev->next = curr->next;
667 }
668 return curr;
669 }
670 prev = curr;
671 curr = curr->next;
672 }
673
674 return nullptr;
675 }
676
678 NodeVectorType nodes_;
681 size_type max_load_factor_percent_ = 75;
682 size_type rehash_threshold_ = 0;
683 static constexpr size_type kDefaultInitialBuckets = 11;
684
691 static constexpr size_type kMaxLoadFactorPercentLimit = 500;
692};
693
694} // namespace pw
Definition: allocator.h:42
Definition: dynamic_deque.h:68
bool try_assign(size_type count, const value_type &value)
Definition: generic_deque.h:618
Definition: dynamic_hash_map.h:72
Definition: dynamic_ptr_vector.h:50
mapped_type & at(const key_type &key)
Definition: dynamic_hash_map.h:395
void rehash(size_type count)
Definition: dynamic_hash_map.h:518
DynamicHashMap(DynamicHashMap &&other) noexcept
Definition: dynamic_hash_map.h:190
insert_return_type insert(const value_type &value)
Inserts a value into the map. Crashes on allocation failure.
Definition: dynamic_hash_map.h:262
std::pair< iterator, bool > emplace(K &&key, Args &&... args)
Constructs an element in-place. Crashes on allocation failure.
Definition: dynamic_hash_map.h:293
void reserve(size_type count)
Definition: dynamic_hash_map.h:552
DynamicHashMap(const DynamicHashMap &)=delete
mapped_type & operator[](const key_type &key)
Definition: dynamic_hash_map.h:413
allocator_type & get_allocator() const
Returns the allocator used by this map.
Definition: dynamic_hash_map.h:215
constexpr DynamicHashMap(Allocator &allocator, const Hash &hash=Hash(), const Equal &equal=Equal()) noexcept
Definition: dynamic_hash_map.h:176
void swap(DynamicHashMap &other) noexcept
Swaps the contents of two maps. No allocations occur.
Definition: dynamic_hash_map.h:352
void max_load_factor_percent(size_type percent)
Definition: dynamic_hash_map.h:484
std::optional< insert_return_type > try_insert(const value_type &value)
Definition: dynamic_hash_map.h:251
void merge(DynamicHashMap &other)
Definition: dynamic_hash_map.h:371
Node * next
Pointer to the next node in the same hash bucket (collision chain).
Definition: dynamic_hash_map.h:81
size_type count(const key_type &key) const
Returns the number of elements with key key (0 or 1).
Definition: dynamic_hash_map.h:418
size_type erase(K &&key)
Definition: dynamic_hash_map.h:342
bool try_reserve(size_type count)
Definition: dynamic_hash_map.h:531
std::pair< const Key, Value > key_value
The stored key-value pair.
Definition: dynamic_hash_map.h:78
std::optional< std::pair< iterator, bool > > try_emplace(K &&key, Args &&... args)
Definition: dynamic_hash_map.h:286
iterator find(const key_type &key)
Definition: dynamic_hash_map.h:425
bool contains(const key_type &key) const
Definition: dynamic_hash_map.h:443
void reset()
Clears the deque and deallocates its buffer.
Definition: dynamic_hash_map.h:243
size_type max_load_factor_percent() const
Definition: dynamic_hash_map.h:478
iterator erase(iterator pos)
Definition: dynamic_hash_map.h:307
DynamicHashMap & operator=(DynamicHashMap &&other) noexcept
Definition: dynamic_hash_map.h:200
SizeType index
Index of this node in the nodes_ vector, enabling O(1) erasure.
Definition: dynamic_hash_map.h:84
iterator erase(const_iterator first, const_iterator last)
Definition: dynamic_hash_map.h:323
bool try_rehash(size_type count)
Definition: dynamic_hash_map.h:494
void clear()
Definition: dynamic_hash_map.h:237
size_type load_factor_percent() const
Definition: dynamic_hash_map.h:469
std::pair< iterator, iterator > equal_range(const key_type &key)
Definition: dynamic_hash_map.h:448
void swap(DynamicDeque &other) noexcept
Swaps the contents of two deques. No allocations occur.
Definition: dynamic_deque.h:203
void reset()
Clears the deque and deallocates its buffer.
Definition: dynamic_deque.h:190
bool Equal(const C1 &c1, const C2 &c2)
Definition: algorithm.h:248
void reset()
Clears the deque and deallocates its buffer.
Definition: dynamic_ptr_vector.h:405
bool try_emplace_back(Args &&... args)
Definition: dynamic_ptr_vector.h:305
void swap(DynamicPtrVector &other)
Swaps the contents of this DynamicPtrVector with another one.
Definition: dynamic_ptr_vector.h:411
bool try_reserve_ptr(size_type new_capacity)
Definition: dynamic_ptr_vector.h:212
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