#ifndef STAR_FLAT_HASH_TABLE_HPP #define STAR_FLAT_HASH_TABLE_HPP #include #include "StarConfig.hpp" namespace Star { template struct FlatHashTable { private: static size_t const EmptyHashValue = 0; static size_t const EndHashValue = 1; static size_t const FilledHashBit = (size_t)1 << (sizeof(size_t) * 8 - 1); struct Bucket { Bucket(); ~Bucket(); Bucket(Bucket const& rhs); Bucket(Bucket&& rhs); Bucket& operator=(Bucket const& rhs); Bucket& operator=(Bucket&& rhs); void setFilled(size_t hash, Value value); void setEmpty(); void setEnd(); Value const* valuePtr() const; Value* valuePtr(); bool isEmpty() const; bool isEnd() const; union { Value value; }; size_t hash; }; typedef std::vector::template rebind_alloc> Buckets; public: struct const_iterator { bool operator==(const_iterator const& rhs) const; bool operator!=(const_iterator const& rhs) const; const_iterator& operator++(); const_iterator operator++(int); Value const& operator*() const; Value const* operator->() const; Bucket const* current; }; struct iterator { bool operator==(iterator const& rhs) const; bool operator!=(iterator const& rhs) const; iterator& operator++(); iterator operator++(int); Value& operator*() const; Value* operator->() const; operator const_iterator() const; Bucket* current; }; FlatHashTable(size_t bucketCount, GetKey const& getKey, Hash const& hash, Equals const& equal, Allocator const& alloc); iterator begin(); iterator end(); const_iterator begin() const; const_iterator end() const; size_t empty() const; size_t size() const; void clear(); pair insert(Value value); iterator erase(const_iterator pos); iterator erase(const_iterator first, const_iterator last); const_iterator find(Key const& key) const; iterator find(Key const& key); void reserve(size_t capacity); Allocator getAllocator() const; bool operator==(FlatHashTable const& rhs) const; bool operator!=(FlatHashTable const& rhs) const; private: static constexpr size_t MinCapacity = 8; static constexpr double MaxFillLevel = 0.7; // Scans for the next bucket value that is non-empty static Bucket* scan(Bucket* p); static Bucket const* scan(Bucket const* p); size_t hashBucket(size_t hash) const; size_t bucketError(size_t current, size_t target) const; void checkCapacity(size_t additionalCapacity); Buckets m_buckets; size_t m_filledCount; GetKey m_getKey; Hash m_hash; Equals m_equals; }; template FlatHashTable::Bucket::Bucket() { this->hash = EmptyHashValue; } template FlatHashTable::Bucket::~Bucket() { if (auto s = valuePtr()) s->~Value(); } template FlatHashTable::Bucket::Bucket(Bucket const& rhs) { this->hash = rhs.hash; if (auto o = rhs.valuePtr()) new (&this->value) Value(*o); } template FlatHashTable::Bucket::Bucket(Bucket&& rhs) { this->hash = rhs.hash; if (auto o = rhs.valuePtr()) new (&this->value) Value(std::move(*o)); } template auto FlatHashTable::Bucket::operator=(Bucket const& rhs) -> Bucket& { if (auto o = rhs.valuePtr()) { if (auto s = valuePtr()) *s = *o; else new (&this->value) Value(*o); } else { if (auto s = valuePtr()) s->~Value(); } this->hash = rhs.hash; return *this; } template auto FlatHashTable::Bucket::operator=(Bucket&& rhs) -> Bucket& { if (auto o = rhs.valuePtr()) { if (auto s = valuePtr()) *s = std::move(*o); else new (&this->value) Value(std::move(*o)); } else { if (auto s = valuePtr()) s->~Value(); } this->hash = rhs.hash; return *this; } template void FlatHashTable::Bucket::setFilled(size_t hash, Value value) { if (auto s = valuePtr()) *s = std::move(value); else new (&this->value) Value(std::move(value)); this->hash = hash | FilledHashBit; } template void FlatHashTable::Bucket::setEmpty() { if (auto s = valuePtr()) s->~Value(); this->hash = EmptyHashValue; } template void FlatHashTable::Bucket::setEnd() { if (auto s = valuePtr()) s->~Value(); this->hash = EndHashValue; } template Value const* FlatHashTable::Bucket::valuePtr() const { if (hash & FilledHashBit) return &this->value; return nullptr; } template Value* FlatHashTable::Bucket::valuePtr() { if (hash & FilledHashBit) return &this->value; return nullptr; } template bool FlatHashTable::Bucket::isEmpty() const { return this->hash == EmptyHashValue; } template bool FlatHashTable::Bucket::isEnd() const { return this->hash == EndHashValue; } template bool FlatHashTable::const_iterator::operator==(const_iterator const& rhs) const { return current == rhs.current; } template bool FlatHashTable::const_iterator::operator!=(const_iterator const& rhs) const { return current != rhs.current; } template auto FlatHashTable::const_iterator::operator++() -> const_iterator& { current = scan(++current); return *this; } template auto FlatHashTable::const_iterator::operator++(int) -> const_iterator { const_iterator copy(*this); operator++(); return copy; } template auto FlatHashTable::const_iterator::operator*() const -> Value const& { return *operator->(); } template auto FlatHashTable::const_iterator::operator->() const -> Value const* { return current->valuePtr(); } template bool FlatHashTable::iterator::operator==(iterator const& rhs) const { return current == rhs.current; } template bool FlatHashTable::iterator::operator!=(iterator const& rhs) const { return current != rhs.current; } template auto FlatHashTable::iterator::operator++() -> iterator& { current = scan(++current); return *this; } template auto FlatHashTable::iterator::operator++(int) -> iterator { iterator copy(*this); operator++(); return copy; } template auto FlatHashTable::iterator::operator*() const -> Value& { return *operator->(); } template auto FlatHashTable::iterator::operator->() const -> Value* { return current->valuePtr(); } template FlatHashTable::iterator::operator typename FlatHashTable::const_iterator() const { return const_iterator{current}; } template FlatHashTable::FlatHashTable(size_t bucketCount, GetKey const& getKey, Hash const& hash, Equals const& equal, Allocator const& alloc) : m_buckets(alloc), m_filledCount(0), m_getKey(getKey), m_hash(hash), m_equals(equal) { if (bucketCount != 0) checkCapacity(bucketCount); } template auto FlatHashTable::begin() -> iterator { if (m_buckets.empty()) return end(); return iterator{scan(m_buckets.data())}; } template auto FlatHashTable::end() -> iterator { return iterator{m_buckets.data() + m_buckets.size() - 1}; } template auto FlatHashTable::begin() const -> const_iterator { return const_cast(this)->begin(); } template auto FlatHashTable::end() const -> const_iterator { return const_cast(this)->end(); } template size_t FlatHashTable::empty() const { return m_filledCount == 0; } template size_t FlatHashTable::size() const { return m_filledCount; } template void FlatHashTable::clear() { if (m_buckets.empty()) return; for (size_t i = 0; i < m_buckets.size() - 1; ++i) m_buckets[i].setEmpty(); m_filledCount = 0; } template auto FlatHashTable::insert(Value value) -> pair { if (m_buckets.empty() || m_filledCount + 1 > (m_buckets.size() - 1) * MaxFillLevel) checkCapacity(1); size_t hash = m_hash(m_getKey(value)) | FilledHashBit; size_t targetBucket = hashBucket(hash); size_t currentBucket = targetBucket; size_t insertedBucket = NPos; while (true) { auto& target = m_buckets[currentBucket]; if (auto entryValue = target.valuePtr()) { if (target.hash == hash && m_equals(m_getKey(*entryValue), m_getKey(value))) return make_pair(iterator{m_buckets.data() + currentBucket}, false); size_t entryTargetBucket = hashBucket(target.hash); size_t entryError = bucketError(currentBucket, entryTargetBucket); size_t addError = bucketError(currentBucket, targetBucket); if (addError > entryError) { if (insertedBucket == NPos) insertedBucket = currentBucket; swap(value, *entryValue); swap(hash, target.hash); targetBucket = entryTargetBucket; } currentBucket = hashBucket(currentBucket + 1); } else { target.setFilled(hash, std::move(value)); ++m_filledCount; if (insertedBucket == NPos) insertedBucket = currentBucket; return make_pair(iterator{m_buckets.data() + insertedBucket}, true); } } } template auto FlatHashTable::erase(const_iterator pos) -> iterator { size_t bucketIndex = pos.current - m_buckets.data(); size_t currentBucketIndex = bucketIndex; auto currentBucket = &m_buckets[currentBucketIndex]; while (true) { size_t nextBucketIndex = hashBucket(currentBucketIndex + 1); auto nextBucket = &m_buckets[nextBucketIndex]; if (auto nextPtr = nextBucket->valuePtr()) { if (bucketError(nextBucketIndex, nextBucket->hash) > 0) { currentBucket->hash = nextBucket->hash; *currentBucket->valuePtr() = std::move(*nextPtr); currentBucketIndex = nextBucketIndex; currentBucket = nextBucket; } else { break; } } else { break; } } m_buckets[currentBucketIndex].setEmpty(); --m_filledCount; return iterator{scan(m_buckets.data() + bucketIndex)}; } template auto FlatHashTable::erase(const_iterator first, const_iterator last) -> iterator { while (first != last) first = erase(first); return iterator{(Bucket*)first.current}; } template auto FlatHashTable::find(Key const& key) const -> const_iterator { return const_cast(this)->find(key); } template auto FlatHashTable::find(Key const& key) -> iterator { if (m_buckets.empty()) return end(); size_t hash = m_hash(key) | FilledHashBit; size_t targetBucket = hashBucket(hash); size_t currentBucket = targetBucket; while (true) { auto& bucket = m_buckets[currentBucket]; if (auto value = bucket.valuePtr()) { if (bucket.hash == hash && m_equals(m_getKey(*value), key)) return iterator{m_buckets.data() + currentBucket}; size_t entryError = bucketError(currentBucket, bucket.hash); size_t findError = bucketError(currentBucket, targetBucket); if (findError > entryError) return end(); currentBucket = hashBucket(currentBucket + 1); } else { return end(); } } } template void FlatHashTable::reserve(size_t capacity) { if (capacity > m_filledCount) checkCapacity(capacity - m_filledCount); } template Allocator FlatHashTable::getAllocator() const { return m_buckets.get_allocator(); } template bool FlatHashTable::operator==(FlatHashTable const& rhs) const { if (size() != rhs.size()) return false; auto i = begin(); auto j = rhs.begin(); auto e = end(); while (i != e) { if (*i != *j) return false; ++i; ++j; } return true; } template bool FlatHashTable::operator!=(FlatHashTable const& rhs) const { return !operator==(rhs); } template constexpr size_t FlatHashTable::MinCapacity; template constexpr double FlatHashTable::MaxFillLevel; template auto FlatHashTable::scan(Bucket* p) -> Bucket* { while (p->isEmpty()) ++p; return p; } template auto FlatHashTable::scan(Bucket const* p) -> Bucket const* { while (p->isEmpty()) ++p; return p; } template size_t FlatHashTable::hashBucket(size_t hash) const { return hash & (m_buckets.size() - 2); } template size_t FlatHashTable::bucketError(size_t current, size_t target) const { return hashBucket(current - target); } template void FlatHashTable::checkCapacity(size_t additionalCapacity) { if (additionalCapacity == 0) return; size_t newSize; if (!m_buckets.empty()) newSize = m_buckets.size() - 1; else newSize = MinCapacity; while ((double)(m_filledCount + additionalCapacity) / (double)newSize > MaxFillLevel) newSize *= 2; if (newSize == m_buckets.size() - 1) return; Buckets oldBuckets; swap(m_buckets, oldBuckets); // Leave an extra end entry when allocating buckets, so that iterators are // simpler and can simply iterate until they find something that is not an // empty entry. m_buckets.resize(newSize + 1); while (m_buckets.capacity() > newSize * 2 + 1) { newSize *= 2; m_buckets.resize(newSize + 1); } m_buckets[newSize].setEnd(); m_filledCount = 0; for (auto& entry : oldBuckets) { if (auto ptr = entry.valuePtr()) insert(std::move(*ptr)); } } } #endif