LibSWOC++ 1.5.14
Solid Wall of C++
Loading...
Searching...
No Matches
IntrusiveHashMap.h
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2// Copyright Apache Software Foundation 2019
10
11#pragma once
12
13#include <vector>
14#include <array>
15#include <algorithm>
16
17#include "swoc/swoc_version.h"
18#include "swoc/IntrusiveDList.h"
19
20namespace swoc { inline namespace SWOC_VERSION_NS {
74template <typename H> class IntrusiveHashMap {
75 using self_type = IntrusiveHashMap;
76
77public:
79 using value_type = typename std::remove_pointer<typename std::remove_reference<decltype(H::next_ptr(nullptr))>::type>::type;
81 using key_type = decltype(H::key_of(static_cast<value_type *>(nullptr)));
83 using hash_id = decltype(H::hash_of(H::key_of(static_cast<value_type *>(nullptr))));
84
91
92protected:
97 using List = IntrusiveDList<H>;
98
100 struct Bucket {
102 struct Linkage {
103 static Bucket *&next_ptr(Bucket *b);
104 static Bucket *&prev_ptr(Bucket *b);
105 Bucket *_prev{nullptr};
106 Bucket *_next{nullptr};
107 } _link;
108
109 value_type *_v{nullptr};
110 size_t _count{0};
111
117 bool _mixed_p{false};
118
122
124 bool contains(value_type *v) const;
125
126 void clear();
127 };
128
129public:
131 static size_t constexpr DEFAULT_BUCKET_COUNT = 7;
133 static size_t constexpr DEFAULT_EXPANSION_LIMIT = 4;
136
137 using iterator = typename List::iterator;
138 using const_iterator = typename List::const_iterator;
139
143 struct range : public std::pair<iterator, iterator> {
144 using super_type = std::pair<iterator, iterator>;
145 using super_type::super_type;
146
147 // These methods enable treating the range as a view in to the hash map.
148
150 iterator const &begin() const;
151
153 iterator const &end() const;
154 };
155
157 struct const_range : public std::pair<const_iterator, const_iterator> {
158 using super_type = std::pair<const_iterator, const_iterator>;
159
162
163 using super_type::super_type;
164
165 // These methods enable treating the range as a view in to the hash map.
166
168 const_iterator const &begin() const;
169
171 const_iterator const &end() const;
172 };
173
177
179 IntrusiveHashMap(self_type &&that) = default;
180
186 self_type &clear();
187
188 iterator begin();
189 const_iterator begin() const;
190 iterator end();
191 const_iterator end() const;
192
198
203 const_iterator find(key_type key) const;
204
205 iterator find(key_type key);
206
211 const_iterator find(value_type const *v) const;
212
213 iterator find(value_type *v);
214
219 const_range equal_range(key_type key) const;
220
221 range equal_range(key_type key);
222
229
230 const_iterator iterator_for(const value_type *v) const;
231
238 iterator erase(iterator const &loc);
239
241 iterator erase(range const &r);
242
244 iterator erase(iterator const &start, iterator const &limit);
245
249 bool erase(value_type *value);
250
261 template <typename F> self_type &apply(F &&f);
262
267 void expand();
268
270 size_t count() const;
271
273 size_t bucket_count() const;
274
277
280
282 self_type &set_expansion_limit(size_t n);
283
285 size_t get_expansion_limit() const;
286
287protected:
289 using Table = std::vector<Bucket>;
290
293
295 IntrusiveDList<typename Bucket::Linkage> _active_buckets;
296
297 Bucket *bucket_for(key_type key);
298
301
302 // noncopyable
303 IntrusiveHashMap(const IntrusiveHashMap &) = delete;
304
305 IntrusiveHashMap &operator=(const IntrusiveHashMap &) = delete;
306
307 // Hash table size prime list.
308 static constexpr std::array<size_t, 29> PRIME = {
309 {1, 3, 7, 13, 31, 61, 127, 251, 509, 1021,
310 2039, 4093, 8191, 16381, 32749, 65521, 131071, 262139, 524287, 1048573,
311 2097143, 4194301, 8388593, 16777213, 33554393, 67108859, 134217689, 268435399, 536870909}
312 };
313};
314
315template <typename H>
316auto
318 return b->_link._next;
319}
320
321template <typename H>
322auto
324 return b->_link._prev;
325}
326
327// This is designed so that if the bucket is empty, then @c nullptr is returned, which will immediately terminate
328// a search loop on an empty bucket because that will start with a nullptr candidate, matching the limit.
329template <typename H>
330auto
332 Bucket *n{_link._next};
333 return n ? n->_v : nullptr;
334};
335
336template <typename H>
337void
339 _v = nullptr;
340 _count = 0;
341 _mixed_p = false;
342 // Need to clear these, or they persist after an expansion which breaks the active bucket list.
343 _link._next = _link._prev = nullptr;
344}
345
346template <typename H>
347bool
349 value_type *x = _v;
350 value_type *limit = this->limit();
351 while (x != limit && x != v) {
352 x = H::next_ptr(x);
353 }
354 return x == v;
355}
356
357// ---------------------
358template <typename H>
359auto
360IntrusiveHashMap<H>::range::begin() const -> iterator const & {
361 return super_type::first;
362}
363
364template <typename H>
365auto
366IntrusiveHashMap<H>::range::end() const -> iterator const & {
367 return super_type::second;
368}
369
370template <typename H>
371auto
372IntrusiveHashMap<H>::const_range::begin() const -> const_iterator const & {
373 return super_type::first;
374}
375
376template <typename H>
377auto
378IntrusiveHashMap<H>::const_range::end() const -> const_iterator const & {
379 return super_type::second;
380}
381
382// ---------------------
383
384template <typename H> IntrusiveHashMap<H>::IntrusiveHashMap(size_t n) {
385 if (n) {
386 _table.resize(*std::lower_bound(PRIME.begin(), PRIME.end(), n));
387 }
388}
389
390template <typename H>
391auto
392IntrusiveHashMap<H>::bucket_for(key_type key) -> Bucket * {
393 return &_table[H::hash_of(key) % _table.size()];
394}
395
396template <typename H>
397auto
399 return _list.begin();
400}
401
402template <typename H>
403auto
404IntrusiveHashMap<H>::begin() const -> const_iterator {
405 return _list.begin();
406}
407
408template <typename H>
409auto
411 return _list.end();
412}
413
414template <typename H>
415auto
416IntrusiveHashMap<H>::end() const -> const_iterator {
417 return _list.end();
418}
419
420template <typename H>
421auto
423 for (auto &b : _table) {
424 b.clear();
425 }
426 // Clear container data.
427 _list.clear();
428 _active_buckets.clear();
429 return *this;
430}
431
432template <typename H>
433auto
434IntrusiveHashMap<H>::find(key_type key) -> iterator {
435 Bucket *b = this->bucket_for(key);
436 value_type *v = b->_v;
437 value_type *limit = b->limit();
438 while (v != limit && !H::equal(key, H::key_of(v))) {
439 v = H::next_ptr(v);
440 }
441 return v == limit ? _list.end() : _list.iterator_for(v);
442}
443
444template <typename H>
445auto
446IntrusiveHashMap<H>::find(key_type key) const -> const_iterator {
447 return const_cast<self_type *>(this)->find(key);
448}
449
450template <typename H>
451auto
452IntrusiveHashMap<H>::equal_range(key_type key) -> range {
453 iterator first{this->find(key)};
454 iterator last{first};
455 iterator limit{this->end()};
456
457 while (last != limit && H::equal(key, H::key_of(&*last))) {
458 ++last;
459 }
460
461 return range{first, last};
462}
463
464template <typename H>
465auto
467 return const_cast<self_type *>(this)->equal_range(key);
468}
469
470template <typename H>
471auto
472IntrusiveHashMap<H>::iterator_for(const value_type *v) const -> const_iterator {
473 return _list.iterator_for(v);
474}
475
476template <typename H>
477auto
479 return _list.iterator_for(v);
480}
481
482template <typename H>
483auto
484IntrusiveHashMap<H>::find(value_type *v) -> iterator {
485 Bucket *b = this->bucket_for(H::key_of(v));
486 return b->contains(v) ? _list.iterator_for(v) : this->end();
487}
488
489template <typename H>
490auto
491IntrusiveHashMap<H>::find(value_type const *v) const -> const_iterator {
492 return const_cast<self_type *>(this)->find(const_cast<value_type *>(v));
493}
494
495template <typename H>
496void
498 auto key = H::key_of(v);
499 Bucket *bucket = this->bucket_for(key);
500 value_type *spot = bucket->_v;
501 bool mixed_p = false; // Found a different key in the bucket.
502
503 if (nullptr == spot) { // currently empty bucket, set it and add to active list.
504 _list.append(v);
505 bucket->_v = v;
506 _active_buckets.append(bucket);
507 } else {
508 value_type *limit = bucket->limit();
509
510 // First search the bucket to see if the key is already in it.
511 while (spot != limit && !H::equal(key, H::key_of(spot))) {
512 spot = H::next_ptr(spot);
513 }
514 if (spot != bucket->_v) {
515 mixed_p = true; // found some other key, it's going to be mixed.
516 }
517 if (spot != limit) {
518 // If an equal key was found, walk past those to insert at the upper end of the range.
519 do {
520 spot = H::next_ptr(spot);
521 } while (spot != limit && H::equal(key, H::key_of(spot)));
522 if (spot != limit) { // something not equal past last equivalent, it's going to be mixed.
523 mixed_p = true;
524 }
525 }
526
527 _list.insert_before(spot, v);
528 if (spot == bucket->_v) { // added before the bucket start, update the start.
529 bucket->_v = v;
530 }
531 bucket->_mixed_p = mixed_p;
532 }
533 ++bucket->_count;
534
535 // auto expand if appropriate.
536 if ((AVERAGE == _expansion_policy && (_list.count() / _table.size()) > _expansion_limit) ||
537 (MAXIMUM == _expansion_policy && bucket->_count > _expansion_limit && bucket->_mixed_p)) {
538 this->expand();
539 }
540}
541
542template <typename H>
543auto
544IntrusiveHashMap<H>::erase(iterator const &loc) -> iterator {
545 value_type *v = loc;
546 iterator zret = ++(this->iterator_for(v)); // get around no const_iterator -> iterator.
547 Bucket *b = this->bucket_for(H::key_of(v));
548 value_type *nv = H::next_ptr(v);
549 value_type *limit = b->limit();
550 if (b->_v == v) { // removed first element in bucket, update bucket
551 if (limit == nv) { // that was also the only element, deactivate bucket
552 _active_buckets.erase(b);
553 b->clear();
554 } else {
555 b->_v = nv;
556 --b->_count;
557 }
558 }
559 _list.erase(loc);
560 return zret;
561}
562
563template <typename H>
564bool
566 auto loc = this->find(value);
567 if (loc != this->end()) {
568 this->erase(loc);
569 return true;
570 }
571 return false;
572}
573
574template <typename H>
575auto
576IntrusiveHashMap<H>::erase(iterator const &start, iterator const &limit) -> iterator {
577 auto spot{start};
578 Bucket *bucket{this->bucket_for(spot)};
579 while (spot != limit) {
580 auto target = bucket;
581 bucket = bucket->_link._next; // bump now to avoid forward iteration problems in case of bucket removal.
582 value_type *v_limit = bucket ? bucket->_v : nullptr;
583 while (spot != v_limit && spot != limit) {
584 --(target->_count);
585 ++spot;
586 }
587 if (target->_count == 0) {
588 _active_buckets.erase(target);
589 }
590 }
591 _list.erase(start, limit);
592 return _list.iterator_for(limit); // convert from const_iterator back to iterator
593};
594
595template <typename H>
596auto
597IntrusiveHashMap<H>::erase(range const &r) -> iterator {
598 return this->erase(r.first, r.second);
599}
600
601namespace detail {
602// Make @c apply more convenient by allowing the function to take a reference type or pointer type to the container
603// elements. The pointer type is the base, plus a shim to convert from a reference type functor to a pointer pointer
604// type. The complex return type definition forces only one, but not both, to be valid for a particular functor. This
605// also must be done via free functions and not method overloads because the compiler forces a match up of method
606// definitions and declarations before any template instantiation.
607
608template <typename H, typename F>
609auto
610IntrusiveHashMapApply(IntrusiveHashMap<H> &map, F &&f)
611 -> decltype(f(*static_cast<typename IntrusiveHashMap<H>::value_type *>(nullptr)), map) {
612 return map.apply([&f](typename IntrusiveHashMap<H>::value_type *v) { return f(*v); });
613}
614
615template <typename H, typename F>
616auto
617IntrusiveHashMapApply(IntrusiveHashMap<H> &map, F &&f)
618 -> decltype(f(static_cast<typename IntrusiveHashMap<H>::value_type *>(nullptr)), map) {
619 auto spot{map.begin()};
620 auto limit{map.end()};
621 while (spot != limit) {
622 f(spot++); // post increment means @a spot is updated before @a f is applied.
623 }
624 return map;
625}
626} // namespace detail
627
628template <typename H>
629template <typename F>
630auto
631IntrusiveHashMap<H>::apply(F &&f) -> self_type & {
632 return detail::IntrusiveHashMapApply(*this, f);
633};
634
635template <typename H>
636void
638 ExpansionPolicy org_expansion_policy = _expansion_policy; // save for restore.
639 value_type *old = _list.head(); // save for repopulating.
640 auto old_size = _table.size();
641
642 // Reset to empty state.
643 this->clear();
644 _table.resize(*std::lower_bound(PRIME.begin(), PRIME.end(), old_size + 1));
645
646 _expansion_policy = MANUAL; // disable any auto expand while we're expanding.
647 while (old) {
648 value_type *v = old;
649 old = H::next_ptr(old);
650 this->insert(v);
651 }
652 // stashed array gets cleaned up when @a tmp goes out of scope.
653 _expansion_policy = org_expansion_policy; // reset to original value.
654}
655
656template <typename H>
657size_t
659 return _list.count();
660}
661
662template <typename H>
663size_t
665 return _table.size();
666}
667
668template <typename H>
669auto
671 _expansion_policy = policy;
672 return *this;
673}
674
675template <typename H>
676auto
680
681template <typename H>
682auto
685 return *this;
686}
687
688template <typename H>
689size_t
693
694}} // namespace swoc::SWOC_VERSION_NS
IntrusiveDList< H > List
bool erase(value_type *value)
const_iterator end() const
Past last element.
void insert(value_type *v)
decltype(H::key_of(static_cast< value_type * >(nullptr))) key_type
Key type for the elements.
const_range equal_range(key_type key) const
ExpansionPolicy
When the hash table is expanded.
@ AVERAGE
Table expands if average chain length exceeds limit. [default].
@ MAXIMUM
Table expands if any chain length exceeds limit.
@ MANUAL
Client must explicitly expand the table.
iterator iterator_for(value_type *v)
ExpansionPolicy get_expansion_policy() const
Get the current expansion policy.
const_iterator begin() const
First element.
List _list
Elements in the table.
self_type & set_expansion_policy(ExpansionPolicy policy)
Set the expansion policy to policy.
size_t bucket_count() const
Number of buckets in the array.
Table _table
Array of buckets.
iterator erase(iterator const &loc)
self_type & apply(F &&f)
iterator erase(range const &r)
Remove all elements in the range r.
self_type & set_expansion_limit(size_t n)
Set the limit value for the expansion policy.
size_t get_expansion_limit() const
Set the limit value for the expansion policy.
iterator begin()
First element.
decltype(H::hash_of(H::key_of(static_cast< value_type * >(nullptr)))) hash_id
The numeric hash ID computed from a key.
IntrusiveDList< typename Bucket::Linkage > _active_buckets
List of non-empty buckets.
ExpansionPolicy _expansion_policy
When to exand the table.
static ExpansionPolicy constexpr DEFAULT_EXPANSION_POLICY
Expansion policy if not specified in constructor.
IntrusiveHashMap(size_t n=DEFAULT_BUCKET_COUNT)
const_iterator find(value_type const *v) const
static size_t constexpr DEFAULT_BUCKET_COUNT
The default starting number of buckets.
iterator end()
Past last element.
std::vector< Bucket > Table
The type of storage for the buckets.
IntrusiveHashMap(self_type &&that)=default
Move constructor.
size_t _expansion_limit
Limit value for expansion.
const_iterator find(key_type key) const
typename std::remove_pointer< typename std::remove_reference< decltype(H::next_ptr(nullptr))>::type >::type value_type
Type of elements in the map.
iterator erase(iterator const &start, iterator const &limit)
Remove all elements in the range (start, limit].
size_t count() const
Number of elements in the map.
static size_t constexpr DEFAULT_EXPANSION_LIMIT
The default expansion policy limit.
For template deduction guides.
Definition ArenaWriter.cc:9
Support for IntrusiveDList<Bucket::Linkage>, definitions and link storage.
static Bucket *& next_ptr(Bucket *b)
Access next pointer.
static Bucket *& prev_ptr(Bucket *b)
Access prev pointer.
A bucket for the hash map.
size_t _count
Number of elements in the bucket.
value_type * _v
First element in the bucket.
A range of constant elements in the map.
std::pair< const_iterator, const_iterator > super_type
Super type.
const_range(range const &r)
Allow implicit conversion of range to const_range.
std::pair< iterator, iterator > super_type
Super type.