30#ifndef AGX_LINEAR_PROBING_HASH_TABLE_H
31#define AGX_LINEAR_PROBING_HASH_TABLE_H
47template <
typename KeyT,
typename ValueT,
typename HashT = agx::HashFn<KeyT>,
typename AllocatorT = ByteAllocator>
52 using PairT = std::pair<KeyT, ValueT>;
53 enum class State : uint8_t
60 const size_t MIN_SIZE = 4;
61 const size_t GROW_FACTOR = 2;
62 const float SHRINK_THRESHOLD = 0.25;
63 const float SMOOTHING_FACTOR = 0.8f;
105 this->goto_next_element();
112 this->goto_next_element();
120 this->goto_previous_element();
129 this->goto_previous_element();
156 void goto_next_element()
161 }
while (_bucket < _map->_num_buckets &&
_map->_states[
_bucket] != State::FILLED);
166 void goto_previous_element()
204 this->goto_next_element();
211 this->goto_next_element();
219 this->goto_previous_element();
228 this->goto_previous_element();
255 void goto_next_element()
260 }
while (_bucket < _map->_num_buckets &&
_map->_states[
_bucket] != State::FILLED);
265 void goto_previous_element()
295 *
this = std::move(other);
300 if (
this == &other ) {
318 for (
size_t bucket=0; bucket<_num_buckets; ++bucket) {
319 if (_states[bucket] == State::FILLED) {
320 _pairs[bucket].~PairT();
324 _allocator.deallocateBytes(_states, _num_buckets *
sizeof(State));
325 _allocator.deallocateBytes(_pairs, _num_buckets *
sizeof(PairT));
334 std::swap(_num_buckets, other._num_buckets);
335 std::swap(_num_filled, other._num_filled);
336 std::swap(_max_probe_length, other._max_probe_length);
348 while (bucket<_num_buckets && _states[bucket] != State::FILLED) {
360 while (bucket<_num_buckets && _states[bucket] != State::FILLED) {
371 {
return iterator(
this, _num_buckets); }
401 return _num_filled==0;
414 if (bucket == (
size_t)-1) {
428 if (bucket == (
size_t)-1)
464 if (bucket != (
size_t)-1) {
465 return &_pairs[bucket].second;
479 if (bucket != (
size_t)-1) {
480 return &_pairs[bucket].second;
489 const ValueT* ret =
try_get(k);
510 auto bucket = find_or_allocate(key);
511 if (_states[bucket] == State::FILLED) {
512 (_pairs + bucket)->second = value;
514 _states[bucket] = State::FILLED;
515 new(_pairs + bucket) PairT(key, value);
524 return insert(p.first, p.second);
552 auto bucket = find_empty_bucket(key);
553 _states[bucket] = State::FILLED;
554 new(_pairs + bucket) PairT(std::move(key), std::move(value));
564 ValueT
set_get(
const KeyT& key,
const ValueT& new_value)
568 auto bucket = find_or_allocate(key);
571 if (_states[bucket] == State::FILLED) {
572 ValueT old_value = _pairs[bucket].second;
573 _pairs[bucket] = new_value.second;
576 _states[bucket] = State::FILLED;
577 new(_pairs + bucket) PairT(key, new_value);
594 auto bucket = find_or_allocate(key);
597 if (_states[bucket] != State::FILLED) {
598 _states[bucket] = State::FILLED;
599 new(_pairs + bucket) PairT(key, ValueT());
603 return _pairs[bucket].second;
615 if (bucket != (
size_t)-1) {
644 for (
size_t bucket = 0; bucket < _num_buckets; ++bucket) {
645 if (_states[bucket] == State::FILLED) {
646 _states[bucket] = State::INACTIVE;
647 _pairs[bucket].~PairT();
651 _max_probe_length = -1;
655 m_smoothingAverage = 0.0;
660 if ((
Real32)_num_filled > m_smoothingAverage)
661 m_smoothingAverage =
Real32( _num_filled );
664 m_smoothingAverage =
Real32( SMOOTHING_FACTOR * m_smoothingAverage + ( 1.0 - SMOOTHING_FACTOR ) * (
Real32)_num_filled );
667 if (m_smoothingAverage < SHRINK_THRESHOLD * (
Real32)_num_buckets && m_smoothingAverage >= (
Real32)MIN_SIZE) {
671 for (
size_t bucket = 0; bucket < _num_buckets; ++bucket) {
672 if (_states[bucket] == State::FILLED) {
673 _states[bucket] = State::INACTIVE;
674 _pairs[bucket].~PairT();
678 _max_probe_length = -1;
688 size_t required_buckets = num_elems + num_elems/2 + 1;
689 if (required_buckets <= _num_buckets) {
692 size_t num_buckets = MIN_SIZE;
693 while (num_buckets < required_buckets)
694 num_buckets *= GROW_FACTOR;
696 auto new_states = (State*)_allocator.allocateBytes(num_buckets *
sizeof(State));
697 auto new_pairs = (PairT*)_allocator.allocateBytes(num_buckets *
sizeof(PairT));
699 if (!new_states || !new_pairs) {
700 _allocator.deallocateBytes(new_states, num_buckets *
sizeof(State));
701 _allocator.deallocateBytes(new_pairs, num_buckets *
sizeof(PairT));
702 throw std::bad_alloc();
706 auto old_num_buckets = _num_buckets;
707 auto old_states = _states;
708 auto old_pairs = _pairs;
711 _num_buckets = num_buckets;
712 _mask = _num_buckets - 1;
713 _states = new_states;
716 std::fill_n(_states, num_buckets, State::INACTIVE);
718 _max_probe_length = -1;
720 for (
size_t src_bucket=0; src_bucket<old_num_buckets; src_bucket++) {
721 if (old_states[src_bucket] == State::FILLED) {
722 auto& src_pair = old_pairs[src_bucket];
724 auto dst_bucket = find_empty_bucket(src_pair.first);
726 agxAssert(_states[dst_bucket] != State::FILLED);
727 _states[dst_bucket] = State::FILLED;
728 new(_pairs + dst_bucket) PairT(std::move(src_pair));
737 _allocator.deallocateBytes(old_states, old_num_buckets *
sizeof(State));
738 _allocator.deallocateBytes(old_pairs, old_num_buckets *
sizeof(PairT));
746 size_t required_buckets = num_elems + num_elems / 2 + 1;
748 size_t num_buckets = MIN_SIZE;
749 while (num_buckets < required_buckets)
750 num_buckets *= GROW_FACTOR;
752 auto new_states = (State*)_allocator.allocateBytes( num_buckets *
sizeof( State ) );
753 auto new_pairs = (PairT*)_allocator.allocateBytes( num_buckets *
sizeof( PairT ) );
755 if (!new_states || !new_pairs) {
756 _allocator.deallocateBytes( new_states, num_buckets *
sizeof( State ) );
757 _allocator.deallocateBytes( new_pairs, num_buckets *
sizeof( PairT ) );
758 throw std::bad_alloc();
761 for (
size_t bucket = 0; bucket < _num_buckets; bucket++)
762 if (_states[bucket] == State::FILLED)
763 _pairs[bucket].~PairT();
765 _allocator.deallocateBytes( _states, _num_buckets *
sizeof( State ) );
766 _allocator.deallocateBytes( _pairs, _num_buckets *
sizeof( PairT ) );
769 _num_buckets = num_buckets;
770 _mask = _num_buckets - 1;
771 _states = new_states;
774 std::fill_n( _states, num_buckets, State::INACTIVE );
776 _max_probe_length = -1;
790 template<
typename T2>
793 if (
empty()) {
return (
size_t)-1; }
795 auto hash_value = _hasher(key);
796 for (
int offset=0; offset<=_max_probe_length; ++offset) {
797 auto bucket = (hash_value + offset) & _mask;
798 if (_states[bucket] == State::FILLED &&
agx::hashKeyEqual(_pairs[bucket].first, key)) {
801 if (_states[bucket] == State::INACTIVE) {
809 _states[bucket] = State::ACTIVE;
810 _pairs[bucket].~PairT();
817 void check_expand_need()
823 size_t find_or_allocate(
const KeyT& key)
825 auto hash_value = _hasher(key);
826 size_t hole = (size_t)-1;
828 for (; offset<=_max_probe_length; ++offset) {
829 auto bucket = (hash_value + offset) & _mask;
831 if (_states[bucket] == State::FILLED) {
835 }
else if (_states[bucket] == State::INACTIVE) {
839 if (hole == (
size_t)-1) {
847 agxAssert(offset == _max_probe_length+1);
849 if (hole != (
size_t)-1) {
855 auto bucket = (hash_value + offset) & _mask;
857 if (_states[bucket] != State::FILLED) {
858 _max_probe_length = offset;
865 size_t find_empty_bucket(
const KeyT& key)
867 auto hash_value = _hasher(key);
868 for (
int offset=0; ; ++offset) {
869 auto bucket = (hash_value + offset) & _mask;
870 if (_states[bucket] != State::FILLED) {
871 if (offset > _max_probe_length) {
872 _max_probe_length = offset;
881 AllocatorT _allocator = AllocatorT();
882 State* _states =
nullptr;
883 PairT* _pairs =
nullptr;
884 size_t _num_buckets = 0;
885 size_t _num_filled = 0;
886 int _max_probe_length = -1;
888 Real32 m_smoothingAverage = 0;
892 template <
typename KeyT,
typename ValueT,
typename HashT,
typename AllocatorT>
895 return table.
begin();
898 template <
typename KeyT,
typename ValueT,
typename HashT,
typename AllocatorT>
905 template <
typename KeyT,
typename ValueT,
typename HashT,
typename AllocatorT>
908 return table.
begin();
911 template <
typename KeyT,
typename ValueT,
typename HashT,
typename AllocatorT>
925 template <
typename KeyT,
typename DataT,
typename HashT = agx::HashFn<KeyT>,
typename AllocatorT = ByteAllocator>
938 template <
typename KeyT,
typename DataT,
typename HashT,
typename AllocatorT>
943 typedef typename Implementation::iterator
iterator;
959 if (bucket == (
size_t)-1) {
962 return iterator(
this, bucket);
968 if (bucket == (
size_t)-1) {
971 return const_iterator(
this, bucket);
978 if (bucket != (
size_t)-1) {
const_iterator & operator++()
const_iterator operator--(int)
Does not do any bounds checking, so only call this if there really is a previous element.
const_iterator(const MyType *hash_map, size_t bucket)
pointer operator->() const
reference operator*() const
bool operator!=(const const_iterator &rhs) const
const_iterator operator++(int)
bool operator==(const const_iterator &rhs) const
std::pair< KeyT, ValueT > bucket_type
const_iterator operator--()
Does not do any bounds checking, so only call this if there really is a previous element.
std::forward_iterator_tag iterator_category
const_iterator(iterator proto)
bool operator!=(const iterator &rhs) const
pointer operator->() const
reference operator*() const
iterator operator--(int)
Does not do any bounds checking, so only call this if there really is a previous element.
bool operator==(const iterator &rhs) const
iterator operator--()
Does not do any bounds checking, so only call this if there really is a previous element.
std::forward_iterator_tag iterator_category
iterator(MyType *hash_map, size_t bucket)
std::pair< KeyT, ValueT > bucket_type
A cache-friendly hash table with open addressing, linear probing and power-of-two capacity.
iterator insert(const KeyT &key, const ValueT value)
Insert a key/value pair into the hash table.
iterator begin()
Iterator to first element in hash table.
bool contains(const KeyT &k) const
Check if the hash table contains a key/value pair for the given key.
void insert(const_iterator begin, const_iterator end)
Insert all elements in the given range.
void clearResize(size_t num_elems)
Resize the table to fit at least num_elems elements and clear all existing entries.
ValueT & operator[](const KeyT &key)
Return the value associated with the given key.
iterator erase(iterator it)
Erase an element from the hash table.
const_iterator end() const
Iterator marking end of hash table.
const ValueT * try_get(const KeyT &k) const
Find the value associated with the given key.
iterator end()
Iterator marking end of hash table.
@ SHRINK_BUFFER_AVERAGED
Buffer is shrunk if a smoothing average (which is updated each clear call) goes below a threshold.
@ MAINTAIN_BUFFER
Buffer is maintained (normal stl behavior).
@ SHRINK_BUFFER
Buffer is deallocated and replaced by an newly allocated empty buffer.
bool erase(const KeyT &key)
Erase an element from the hash table.
void clear(int policy=SHRINK_BUFFER_AVERAGED)
Remove all elements.
const PairT & const_reference
LinearProbingHashTableImplementation & operator=(const LinearProbingHashTableImplementation &other)
void insert_unique(KeyT &&key, ValueT &&value)
Insert a key/value pair into the hash table assuming that the given key does not already exist in the...
ValueT * try_get(const KeyT &k)
Find the value associated with the given key.
void insert_unique(std::pair< KeyT, ValueT > &&p)
ValueT set_get(const KeyT &key, const ValueT &new_value)
void erase_bucket(size_t bucket)
const_iterator find(const KeyT &key) const
Find a key/value pair in the hash table given a key.
const ValueT get_or_return_default(const KeyT &k) const
~LinearProbingHashTableImplementation()
LinearProbingHashTableImplementation & operator=(LinearProbingHashTableImplementation &&other)
void reserve(size_t num_elems)
Make room for this many elements in the hash table.
LinearProbingHashTableImplementation(LinearProbingHashTableImplementation &&other)
void swap(LinearProbingHashTableImplementation &other)
iterator find(const KeyT &key)
Find a key/value pair in the hash table given a key.
LinearProbingHashTableImplementation()=default
size_t find_filled_bucket(const T2 &key) const
const_iterator begin() const
Iterator to first element in hash table.
size_t count(const KeyT &k) const
Count the number of key/value pairs matching the given key.
iterator insert(const std::pair< KeyT, ValueT > &p)
LinearProbingHashTableImplementation(const LinearProbingHashTableImplementation &other)
bool contains(const KeyT *key) const
const_iterator find(const KeyT *key) const
Implementation::iterator iterator
bool erase(const KeyT *key)
Implementation::const_iterator const_iterator
iterator find(const KeyT *key)
LinearProbingHashTableImplementation< agx::ref_ptr< KeyT >, DataT, HashT, AllocatorT > Implementation
Inheritance with partial specialization due to bug with ref_ptr containers.
LinearProbingHashTableImplementation< KeyT, DataT, HashT, AllocatorT > Implementation
Smart pointer for handling referenced counted objects.
The agx namespace contains the dynamics/math part of the AGX Dynamics API.
LinearProbingHashSetImplementation< KeyT, HashT >::const_iterator cend(const LinearProbingHashSetImplementation< KeyT, HashT > &set)
LinearProbingHashSetImplementation< KeyT, HashT >::const_iterator cbegin(const LinearProbingHashSetImplementation< KeyT, HashT > &set)
LinearProbingHashSetImplementation< KeyT, HashT >::iterator end(LinearProbingHashSetImplementation< KeyT, HashT > &set)
LinearProbingHashSetImplementation< KeyT, HashT >::iterator begin(LinearProbingHashSetImplementation< KeyT, HashT > &set)
bool hashKeyEqual(const T1 &key1, const T2 &key2)
void swap(agx::Name &lhs, agx::Name &rhs)