co_ecs 0.9.0
Cobalt ECS
Loading...
Searching...
No Matches
hash_table.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <cassert>
4#include <memory>
5#include <stdexcept>
6#include <vector>
7
9
10namespace co_ecs::detail {
11
16constexpr auto approx_85_percent(auto value) noexcept -> decltype(auto) {
17 return (value * 870) >> 10U; // NOLINT(readability-magic-numbers)
18}
19
24constexpr auto approx_40_percent(auto value) noexcept -> decltype(auto) {
25 return (value * 409) >> 10U; // NOLINT(readability-magic-numbers)
26}
27
37template<typename K, typename T, bool is_map, typename Hash, typename KeyEqual, typename Allocator>
38class hash_table {
39public:
40 using key_type = K;
41 using mapped_type = T;
42 using value_type = std::conditional_t<is_map, std::pair<key_type, mapped_type>, key_type>;
43 using allocator_type = Allocator;
44 using allocator_traits = std::allocator_traits<allocator_type>;
45 using hasher = Hash;
46 using key_equal = KeyEqual;
47 using size_type = std::size_t;
48
49 constexpr static size_type default_bucket_count = 16; // the number of buckets the hash table is initialized with
50
51private:
53 struct bucket_info {
54 bool occupied{ false };
55 size_t hash{};
56 size_t psl{};
57 };
58
59 using info_storage = std::vector<bucket_info>;
60 using info_iterator = typename info_storage::iterator;
61 using info_const_iterator = typename info_storage::const_iterator;
62
64 class buckets {
65 public:
70 constexpr buckets(const allocator_type& alloc, typename allocator_type::size_type size) :
71 a(alloc), b(a.allocate(size)), e(b + size), z(e) {
72 }
73
77 constexpr buckets(const buckets& rhs) = delete;
78
82 constexpr buckets(buckets&& rhs) noexcept {
83 swap(std::move(rhs));
84 }
85
90 constexpr auto operator=(const buckets& rhs) -> buckets& = delete;
91
96 constexpr auto operator=(buckets&& rhs) noexcept -> buckets& {
97 swap(std::move(rhs));
98 return *this;
99 }
100
102 constexpr ~buckets() {
103 a.deallocate(b, e - b);
104 }
105
109 void swap(buckets&& rhs) noexcept {
110 std::swap(a, rhs.a);
111 std::swap(b, rhs.b);
112 std::swap(e, rhs.e);
113 std::swap(z, rhs.z);
114 }
115
119 constexpr auto allocator() noexcept -> allocator_type& {
120 return a;
121 }
122
126 constexpr auto allocator() const noexcept -> const allocator_type& {
127 return a;
128 }
129
133 constexpr auto begin() noexcept -> value_type* {
134 return b;
135 }
136
140 constexpr auto begin() const noexcept -> const value_type* {
141 return b;
142 }
143
147 constexpr auto end() noexcept -> value_type* {
148 return e;
149 }
150
154 constexpr auto end() const noexcept -> const value_type* {
155 return e;
156 }
157
161 [[nodiscard]] constexpr auto size() const noexcept -> size_type {
162 return e - b;
163 }
164
165 private:
166 allocator_type a{};
167 value_type* b{};
168 value_type* e{};
169 value_type* z{};
170 };
171
175 template<bool is_const = true>
176 class iterator_impl {
177 public:
178 using iterator_concept = std::forward_iterator_tag;
179 using iterator_category = std::forward_iterator_tag;
180 using difference_type = int;
181 using value_type = hash_table::value_type;
182 using element_type = hash_table::value_type;
183 using reference = std::conditional_t<is_const, const value_type&, value_type&>;
184 using pointer = std::conditional_t<is_const, const value_type*, value_type*>;
185 using info_iter = std::conditional_t<is_const, info_const_iterator, info_iterator>;
186
187 constexpr iterator_impl() = default;
188 constexpr ~iterator_impl() = default;
189
195 constexpr explicit iterator_impl(pointer ptr, pointer end, info_iter info) noexcept :
196 _ptr(ptr), _end(end), _info(info) {
197 // fast forward to the next occupied entry in the buckets array
198 // if not occupied already or it is not the end.
199 if (_ptr != _end && !_info->occupied) {
200 fast_forward();
201 }
202 }
203
204 constexpr iterator_impl(const iterator_impl& other) noexcept = default;
205 constexpr auto operator=(const iterator_impl& other) noexcept -> iterator_impl& = default;
206 constexpr iterator_impl(iterator_impl&& other) noexcept = default;
207 constexpr auto operator=(iterator_impl&& other) noexcept -> iterator_impl& = default;
208
212 constexpr auto operator++() noexcept -> iterator_impl& {
213 _info++;
214 _ptr++;
215 fast_forward();
216 return *this;
217 }
218
222 constexpr auto operator++(int) noexcept -> iterator_impl {
223 iterator_impl tmp(*this);
224 *this ++;
225 return tmp;
226 }
227
231 constexpr auto operator*() const noexcept -> reference {
232 return *_ptr;
233 }
234
238 constexpr auto operator->() const noexcept -> pointer {
239 return _ptr;
240 }
241
246 constexpr auto operator<=>(const iterator_impl& rhs) const noexcept = default;
247
248 private:
249 constexpr void fast_forward() noexcept {
250 while (_ptr != _end && !_info->occupied) {
251 _info++;
252 _ptr++;
253 }
254 }
255
256 pointer _ptr{};
257 pointer _end{};
258 info_iter _info{};
259 };
260
261public:
262 using iterator = iterator_impl<false>;
263 using const_iterator = iterator_impl<true>;
264
266 constexpr hash_table() : _buckets(allocator_type(), default_bucket_count), _info(default_bucket_count) {
267 }
268
270 constexpr ~hash_table() {
271 for (auto& value : *this) {
272 allocator_traits::destroy(_buckets.allocator(), std::addressof(value));
273 }
274 }
275
282 constexpr explicit hash_table(size_type bucket_count,
283 const Hash& hash = Hash(),
284 const key_equal& equal = key_equal(),
285 const Allocator& alloc = Allocator()) :
286 _buckets(alloc, bucket_count), _info(bucket_count), _hash(hash), _equal(equal) {
287 assert((bucket_count % 2 == 0) && "Bucket count must be a power of two");
288 }
289
293 constexpr explicit hash_table(const Allocator& alloc) : hash_table(default_bucket_count, alloc) {
294 }
295
300 constexpr hash_table(size_type bucket_count, const Allocator& alloc) : hash_table(bucket_count, Hash(), alloc) {
301 }
302
308 constexpr hash_table(size_type bucket_count, const Hash& hash, const Allocator& alloc) :
309 hash_table(bucket_count, hash, key_equal(), alloc) {
310 }
311
322 template<typename input_iterator>
323 constexpr hash_table(input_iterator first,
324 input_iterator last,
325 size_type bucket_count = default_bucket_count,
326 const Hash& hash = Hash(),
327 const key_equal& equal = key_equal(),
328 const Allocator& alloc = Allocator()) :
329 _buckets(alloc, bucket_count), _info(default_bucket_count), _equal(equal), _hash(hash) {
330 assert((bucket_count % 2 == 0) && "Bucket count must be a power of two");
331 for (; first != last; ++first) {
332 emplace_or_assign_impl(std::move(*first));
333 }
334 }
335
344 template<typename input_iterator>
345 constexpr hash_table(input_iterator first,
346 input_iterator last,
347 size_type bucket_count,
348 const Allocator& alloc = Allocator()) : hash_table(first, last, bucket_count, Hash(), key_equal(), alloc) {
349 }
350
360 template<typename input_iterator>
361 constexpr hash_table(input_iterator first,
362 input_iterator last,
363 size_type bucket_count,
364 const Hash& hash,
365 const Allocator& alloc = Allocator()) : hash_table(first, last, bucket_count, hash, key_equal(), alloc) {
366 }
367
375 constexpr hash_table(std::initializer_list<value_type> init,
376 size_type bucket_count = default_bucket_count,
377 const Hash& hash = Hash(),
378 const KeyEqual& equal = KeyEqual(),
379 const Allocator& alloc = Allocator()) : hash_table(init.begin(), init.end(), bucket_count, hash, equal, alloc) {
380 }
381
385 constexpr hash_table(const hash_table& rhs) :
386 _info(rhs._info), _size(rhs._size), _hash(rhs._hash), _equal(rhs._equal),
387 _buckets(rhs._buckets.allocator(), rhs._buckets.size()) {
388 for (std::size_t idx{}; const auto& info : _info) {
389 if (info.occupied) {
390 std::uninitialized_copy_n(rhs._buckets.begin() + idx, 1, _buckets.begin() + idx);
391 }
392 idx++;
393 }
394 }
395
399 constexpr auto operator=(const hash_table& rhs) -> hash_table& {
400 hash_table copy(rhs);
401 swap(std::move(copy));
402 return *this;
403 }
404
408 constexpr hash_table(hash_table&& rhs) noexcept {
409 swap(std::move(rhs));
410 }
411
415 constexpr auto operator=(hash_table&& rhs) noexcept -> hash_table& {
416 swap(std::move(rhs));
417 return *this;
418 }
419
423 constexpr void swap(hash_table&& rhs) {
424 std::swap(_buckets, rhs._buckets);
425 std::swap(_info, rhs._info);
426 std::swap(_size, rhs._size);
427 std::swap(_equal, rhs._equal);
428 std::swap(_hash, rhs._hash);
429 }
430
434 constexpr auto get_allocator() const noexcept -> allocator_type {
435 return _buckets.allocator();
436 }
437
442 constexpr auto operator[](const key_type& key) -> mapped_type&
443 requires(is_map)
444 {
445 if (auto iter = find(key); iter != end()) {
446 return iter->second;
447 }
448 auto [iter, _] = emplace_impl(std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple());
449 return iter->second;
450 }
451
458 auto at(const key_type& key) -> mapped_type&
459 requires(is_map)
460 {
461 if (auto iter = find_impl(*this, key); iter != end()) {
462 return iter->second;
463 }
464 throw std::out_of_range{ "key not found in hash_table" };
465 }
466
473 auto at(const key_type& key) const -> const mapped_type&
474 requires(is_map)
475 {
476 if (auto iter = find_impl(*this, key); iter != end()) {
477 return iter->second;
478 }
479 throw std::out_of_range{ "key not found in hash_table" };
480 }
481
483 void clear() noexcept {
484 // destroy held elements
485 for (auto& value : *this) {
486 allocator_traits::destroy(_buckets.allocator(), std::addressof(value));
487 }
488 // clear the bucket info
489 for (auto& info : _info) {
490 info = bucket_info{};
491 }
492 _size = 0;
493 }
494
500 auto insert(const value_type& value) -> std::pair<iterator, bool> {
501 return emplace_impl(value);
502 }
503
509 auto insert([[maybe_unused]] iterator hint, value_type&& value) -> iterator {
510 return emplace_impl(std::move(value)).first;
511 }
512
518 auto insert([[maybe_unused]] iterator hint, const value_type& value) -> iterator {
519 return emplace_impl(value).first;
520 }
521
527 auto insert_or_assign(const value_type& value) -> std::pair<iterator, bool> {
528 return emplace_or_assign_impl(value);
529 }
530
538 template<typename... Args>
539 auto emplace(Args&&... args) -> std::pair<iterator, bool> {
540 return emplace_impl(std::forward<Args>(args)...);
541 }
542
546 void erase(const key_type& key) {
547 return erase_impl(key);
548 }
549
554 template<typename C>
555 void erase(C&& comparable_key) {
556 return erase_impl(std::forward<C>(comparable_key));
557 }
558
562 void erase(const_iterator pos) {
563 return erase_impl(get_key(*pos));
564 }
565
569 void erase(iterator pos) {
570 return erase_impl(get_key(*pos));
571 }
572
578 [[nodiscard]] auto count(const key_type& key) const noexcept -> size_type {
579 return find_impl(*this, key) != end() ? 1 : 0;
580 }
581
587 [[nodiscard]] auto contains(const key_type& key) const noexcept -> bool {
588 return count(key) > 0;
589 }
590
595 [[nodiscard]] auto find(const auto& key) noexcept -> iterator {
596 return find_impl(*this, key);
597 }
598
603 [[nodiscard]] auto find(const key_type& key) const noexcept -> const_iterator {
604 return find_impl(*this, key);
605 }
606
610 void reserve(size_type new_size) {
611 if (new_size < _size) {
612 return;
613 }
614
615 hash_table h_table(new_size, _hash, _equal, _buckets.allocator());
616
617 for (auto& value : *this) {
618 h_table._emplace_impl(std::move(value));
619 }
620
621 std::swap(_buckets, h_table._buckets);
622 std::swap(_info, h_table._info);
623 std::swap(_size, h_table._size);
624 std::swap(_equal, h_table._equal);
625 std::swap(_hash, h_table._hash);
626 }
627
631 [[nodiscard]] constexpr auto begin() noexcept -> iterator {
632 return iterator{ _buckets.begin(), _buckets.end(), _info.begin() };
633 }
634
638 [[nodiscard]] constexpr auto end() noexcept -> iterator {
639 return iterator{ _buckets.end(), _buckets.end(), _info.end() };
640 }
641
645 [[nodiscard]] constexpr auto begin() const noexcept -> const_iterator {
646 return const_iterator{ _buckets.begin(), _buckets.end(), _info.begin() };
647 }
648
652 [[nodiscard]] constexpr auto end() const noexcept -> const_iterator {
653 return const_iterator{ _buckets.end(), _buckets.end(), _info.end() };
654 }
655
659 [[nodiscard]] constexpr auto cbegin() const noexcept -> iterator {
660 return begin();
661 }
662
666 [[nodiscard]] constexpr auto cend() const noexcept -> iterator {
667 return end();
668 }
669
673 [[nodiscard]] constexpr auto size() const noexcept -> size_type {
674 return _size;
675 }
676
681 [[nodiscard]] constexpr auto empty() const noexcept -> bool {
682 return size() == 0;
683 }
684
685private:
686 template<typename C>
687 void erase_impl(C&& key) {
688 size_type bucket_size = _buckets.size();
689
690 size_type hash = _hash(key);
691 size_type psl = 0;
692 size_type index = mod_2n(hash, bucket_size);
693
694 value_type* ptr{};
695 auto info = _info.begin();
696 while (true) {
697 ptr = _buckets.begin() + index;
698 info = _info.begin() + index;
699
700 if (!info->occupied || psl > info->psl) {
701 return;
702 }
703
704 if (info->hash != hash || !_equal(get_key(*ptr), key)) {
705 index = mod_2n(index + 1, bucket_size);
706 psl++;
707 continue;
708 }
709
710 break;
711 }
712
713 value_type temp = std::move(*ptr);
714 _size--;
715
716 while (true) {
717 info->occupied = false;
718
719 index = mod_2n(index + 1, bucket_size);
720 auto nptr = _buckets.begin() + index;
721 auto n_info = _info.begin() + index;
722
723 if (!n_info->occupied || n_info->psl == 0) {
724 break;
725 }
726
727 n_info->psl--;
728 *ptr = std::move(*nptr);
729 ptr = nptr;
730 *info = std::move(*n_info);
731 info = n_info;
732 }
733
734 const size_type threshold = approx_40_percent(bucket_size);
735 if (_size > default_bucket_count && _size < threshold) {
736 const auto new_size = bucket_size >> size_type{ 1 };
737 reserve(new_size);
738 }
739 }
740
741 template<typename... Args>
742 constexpr auto emplace_impl(Args&&... args) -> std::pair<iterator, bool> {
743 size_type buckets_size = _buckets.size();
744 const size_type threshold = approx_85_percent(buckets_size);
745 if (_size > threshold) {
746 const auto new_size = buckets_size << size_type{ 1 };
747 reserve(new_size);
748 }
749
750 return _emplace_impl(std::forward<Args>(args)...);
751 }
752
753 template<typename... Args>
754 constexpr auto emplace_or_assign_impl(Args&&... args) -> std::pair<iterator, bool> {
755 size_type buckets_size = _buckets.size();
756 const size_type threshold = approx_85_percent(buckets_size);
757 if (_size > threshold) {
758 const auto new_size = buckets_size << size_type{ 1 };
759 reserve(new_size);
760 }
761 return _emplace_or_assign_impl(std::forward<Args>(args)...);
762 }
763
764 template<typename... Args>
765 constexpr auto _emplace_or_assign_impl(Args&&... args) -> std::pair<iterator, bool> {
766 size_type buckets_size = _buckets.size();
767 value_type temp(std::forward<Args>(args)...);
768 size_type hash = _hash(get_key(temp));
769 size_type psl = 0;
770 bucket_info n_info = { true, hash, psl };
771 size_type index = mod_2n(hash, buckets_size);
772
773 value_type* ret{ nullptr };
774 info_iterator ret_info{};
775
776 while (true) {
777 value_type* ptr = _buckets.begin() + index;
778 auto info = _info.begin() + index;
779 if (info->occupied) {
780 if (info->hash == hash && _equal(get_key(*ptr), get_key(temp))) {
781 get_value(*ptr) = std::move(get_value(temp));
782 return std::make_pair(create_iterator(ptr, info), false);
783 }
784
785 if (n_info.psl > info->psl) {
786 std::swap(temp, *ptr);
787 std::swap(n_info, *info);
788 if (!ret) {
789 ret = ptr;
790 ret_info = info;
791 }
792 }
793 n_info.psl++;
794 index = mod_2n(index + 1, buckets_size);
795 continue;
796 }
797 allocator_traits::construct(_buckets.allocator(), ptr, std::move(temp));
798 *info = n_info;
799 if (!ret) {
800 ret = ptr;
801 ret_info = info;
802 }
803 _size++;
804 return std::make_pair(create_iterator(ret, ret_info), true);
805 }
806 }
807
808 template<typename... Args>
809 constexpr auto _emplace_impl(Args&&... args) -> decltype(auto) {
810 size_type buckets_size = _buckets.size();
811 value_type temp(std::forward<Args>(args)...);
812 size_type hash = _hash(get_key(temp));
813 size_type psl = 0;
814 bucket_info n_info = { true, hash, psl };
815 size_type index = mod_2n(hash, buckets_size);
816
817 value_type* ret{ nullptr };
818 info_iterator ret_info{};
819
820 while (true) {
821 value_type* ptr = _buckets.begin() + index;
822 auto info = _info.begin() + index;
823 if (info->occupied) {
824 if (info->hash == hash && _equal(get_key(*ptr), get_key(temp))) {
825 return std::make_pair(create_iterator(ptr, info), false);
826 }
827
828 if (n_info.psl > info->psl) {
829 std::swap(temp, *ptr);
830 std::swap(n_info, *info);
831 if (!ret) {
832 ret = ptr;
833 ret_info = info;
834 }
835 }
836 n_info.psl++;
837 index = mod_2n(index + 1, buckets_size);
838 continue;
839 }
840 allocator_traits::construct(_buckets.allocator(), ptr, std::move(temp));
841 *info = n_info;
842 if (!ret) {
843 ret = ptr;
844 ret_info = info;
845 }
846 _size++;
847 return std::make_pair(create_iterator(ret, ret_info), true);
848 }
849 }
850
851 static constexpr auto find_impl(auto& self, const key_type& key) -> decltype(auto) {
852 size_type buckets_size = self._buckets.size();
853 if (!buckets_size) {
854 return self.end();
855 }
856 size_type hash = self._hash(key);
857 size_type index = mod_2n(hash, buckets_size);
858
859 size_type probes = 0;
860 while (true) {
861 auto ptr = self._buckets.begin() + index;
862 auto info = self._info.begin() + index;
863 if (info->occupied && info->hash == hash && self._equal(get_key(*ptr), key)) {
864 return self.create_iterator(ptr, info);
865 }
866 if (!info->occupied || probes > info->psl) {
867 return self.end();
868 }
869 probes++;
870 index = mod_2n(index + 1, buckets_size);
871 }
872 }
873
874 constexpr auto create_iterator(value_type* ptr, info_iterator info) noexcept -> iterator {
875 return iterator{ ptr, _buckets.end(), info };
876 }
877
878 constexpr auto create_iterator(const value_type* ptr, info_const_iterator info) const noexcept -> const_iterator {
879 return const_iterator{ ptr, _buckets.end(), info };
880 }
881
882 constexpr static auto get_key(auto& entry) -> auto& {
883 if constexpr (is_map) {
884 return entry.first;
885 } else {
886 return entry;
887 }
888 }
889
890 constexpr static auto get_value(auto& entry) -> auto& {
891 if constexpr (is_map) {
892 return entry.second;
893 } else {
894 return entry;
895 }
896 }
897
898 size_t _size{};
899 buckets _buckets{ allocator_type(), default_bucket_count };
900 info_storage _info{ default_bucket_count };
901 key_equal _equal{};
902 hasher _hash{};
903};
904
905} // namespace co_ecs::detail
Definition component.hpp:17
constexpr auto mod_2n(auto value, auto divisor) noexcept -> decltype(auto)
Calculate the value % b=2^n.
Definition bits.hpp:10
constexpr auto approx_40_percent(auto value) noexcept -> decltype(auto)
Approximately calculate 40% of passed value. 40% is used as a reserve threshold.
Definition hash_table.hpp:24
constexpr auto approx_85_percent(auto value) noexcept -> decltype(auto)
Approximately calculate 85% of passed value. 85% is used as a reserve threshold.
Definition hash_table.hpp:16