osb/source/core/StarFlatHashTable.hpp
Kae e6f96cb2bd std::allocator::rebind is deprecated
Clang should no longer scream
2024-02-20 19:34:58 +11:00

558 lines
20 KiB
C++

#ifndef STAR_FLAT_HASH_TABLE_HPP
#define STAR_FLAT_HASH_TABLE_HPP
#include <vector>
#include "StarConfig.hpp"
namespace Star {
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
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<Bucket, typename std::allocator_traits<Allocator>::template rebind_alloc<Bucket>> 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<iterator, bool> 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 <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::Bucket::Bucket() {
this->hash = EmptyHashValue;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::Bucket::~Bucket() {
if (auto s = valuePtr())
s->~Value();
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::Bucket::Bucket(Bucket const& rhs) {
this->hash = rhs.hash;
if (auto o = rhs.valuePtr())
new (&this->value) Value(*o);
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::Bucket::Bucket(Bucket&& rhs) {
this->hash = rhs.hash;
if (auto o = rhs.valuePtr())
new (&this->value) Value(std::move(*o));
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::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 <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::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 <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
void FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::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 <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
void FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::Bucket::setEmpty() {
if (auto s = valuePtr())
s->~Value();
this->hash = EmptyHashValue;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
void FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::Bucket::setEnd() {
if (auto s = valuePtr())
s->~Value();
this->hash = EndHashValue;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
Value const* FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::Bucket::valuePtr() const {
if (hash & FilledHashBit)
return &this->value;
return nullptr;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
Value* FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::Bucket::valuePtr() {
if (hash & FilledHashBit)
return &this->value;
return nullptr;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
bool FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::Bucket::isEmpty() const {
return this->hash == EmptyHashValue;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
bool FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::Bucket::isEnd() const {
return this->hash == EndHashValue;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
bool FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::const_iterator::operator==(const_iterator const& rhs) const {
return current == rhs.current;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
bool FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::const_iterator::operator!=(const_iterator const& rhs) const {
return current != rhs.current;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::const_iterator::operator++() -> const_iterator& {
current = scan(++current);
return *this;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::const_iterator::operator++(int) -> const_iterator {
const_iterator copy(*this);
operator++();
return copy;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::const_iterator::operator*() const -> Value const& {
return *operator->();
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::const_iterator::operator->() const -> Value const* {
return current->valuePtr();
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
bool FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::iterator::operator==(iterator const& rhs) const {
return current == rhs.current;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
bool FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::iterator::operator!=(iterator const& rhs) const {
return current != rhs.current;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::iterator::operator++() -> iterator& {
current = scan(++current);
return *this;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::iterator::operator++(int) -> iterator {
iterator copy(*this);
operator++();
return copy;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::iterator::operator*() const -> Value& {
return *operator->();
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::iterator::operator->() const -> Value* {
return current->valuePtr();
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::iterator::operator typename FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::const_iterator() const {
return const_iterator{current};
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::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 <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::begin() -> iterator {
if (m_buckets.empty())
return end();
return iterator{scan(m_buckets.data())};
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::end() -> iterator {
return iterator{m_buckets.data() + m_buckets.size() - 1};
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::begin() const -> const_iterator {
return const_cast<FlatHashTable*>(this)->begin();
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::end() const -> const_iterator {
return const_cast<FlatHashTable*>(this)->end();
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
size_t FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::empty() const {
return m_filledCount == 0;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
size_t FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::size() const {
return m_filledCount;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
void FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::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 <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::insert(Value value) -> pair<iterator, bool> {
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 <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::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 <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::erase(const_iterator first, const_iterator last) -> iterator {
while (first != last)
first = erase(first);
return iterator{(Bucket*)first.current};
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::find(Key const& key) const -> const_iterator {
return const_cast<FlatHashTable*>(this)->find(key);
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::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 <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
void FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::reserve(size_t capacity) {
if (capacity > m_filledCount)
checkCapacity(capacity - m_filledCount);
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
Allocator FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::getAllocator() const {
return m_buckets.get_allocator();
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
bool FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::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 <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
bool FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::operator!=(FlatHashTable const& rhs) const {
return !operator==(rhs);
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
constexpr size_t FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::MinCapacity;
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
constexpr double FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::MaxFillLevel;
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::scan(Bucket* p) -> Bucket* {
while (p->isEmpty())
++p;
return p;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
auto FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::scan(Bucket const* p) -> Bucket const* {
while (p->isEmpty())
++p;
return p;
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
size_t FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::hashBucket(size_t hash) const {
return hash & (m_buckets.size() - 2);
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
size_t FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::bucketError(size_t current, size_t target) const {
return hashBucket(current - target);
}
template <typename Value, typename Key, typename GetKey, typename Hash, typename Equals, typename Allocator>
void FlatHashTable<Value, Key, GetKey, Hash, Equals, Allocator>::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