Веб-сайт самохостера Lotigara

summaryrefslogtreecommitdiff
path: root/source/core/StarFlatHashTable.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/core/StarFlatHashTable.hpp')
-rw-r--r--source/core/StarFlatHashTable.hpp557
1 files changed, 557 insertions, 0 deletions
diff --git a/source/core/StarFlatHashTable.hpp b/source/core/StarFlatHashTable.hpp
new file mode 100644
index 0000000..a13e08a
--- /dev/null
+++ b/source/core/StarFlatHashTable.hpp
@@ -0,0 +1,557 @@
+#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 Allocator::template rebind<Bucket>::other> 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(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 = move(*o);
+ else
+ new (&this->value) Value(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 = move(value);
+ else
+ new (&this->value) Value(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, 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() = 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(move(*ptr));
+ }
+}
+
+}
+
+#endif