diff --git a/BAN/include/BAN/HashSet.h b/BAN/include/BAN/HashSet.h new file mode 100644 index 00000000..40517f84 --- /dev/null +++ b/BAN/include/BAN/HashSet.h @@ -0,0 +1,299 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace BAN +{ + + template + class HashSetIterator; + + template> + class HashSet + { + public: + using value_type = T; + using size_type = hash_t; + using const_iterator = HashSetIterator; + + public: + HashSet() = default; + HashSet(const HashSet&); + HashSet(HashSet&&); + + HashSet& operator=(const HashSet&); + HashSet& operator=(HashSet&&); + + [[nodiscard]] ErrorOr insert(const T&); + [[nodiscard]] ErrorOr insert(T&&); + void remove(const T&); + void clear(); + + const_iterator begin() const { return const_iterator(this, m_buckets.begin()); } + const_iterator end() const { return const_iterator(this, m_buckets.end()); } + + bool contains(const T&) const; + + size_type size() const; + bool empty() const; + + private: + [[nodiscard]] ErrorOr rebucket(size_type); + Vector& get_bucket(const T&); + const Vector& get_bucket(const T&) const; + + private: + Vector> m_buckets; + size_type m_size = 0; + + friend class HashSetIterator; + }; + + template + class HashSetIterator + { + public: + HashSetIterator() = default; + HashSetIterator(const HashSetIterator&); + + HashSetIterator& operator++(); + HashSetIterator operator++(int); + + const T& operator*() const; + const T* operator->() const; + + bool operator==(const HashSetIterator&) const; + bool operator!=(const HashSetIterator&) const; + + operator bool() const { return m_owner && m_current_bucket; } + + private: + HashSetIterator(const HashSet* owner, Vector>::const_iterator bucket); + void find_next(); + + private: + const HashSet* m_owner = nullptr; + Vector>::const_iterator m_current_bucket; + Vector::const_iterator m_current_key; + + friend class HashSet; + }; + + + + template + HashSet::HashSet(const HashSet& other) + : m_buckets(other.m_buckets) + , m_size(other.m_size) + { + } + + template + HashSet::HashSet(HashSet&& other) + : m_buckets(move(other.m_buckets)) + , m_size(other.m_size) + { + other.clear(); + } + + template + HashSet& HashSet::operator=(const HashSet& other) + { + clear(); + m_buckets = other.m_buckets; + m_size = other.m_size; + return *this; + } + + template + HashSet& HashSet::operator=(HashSet&& other) + { + clear(); + m_buckets = move(other.m_buckets); + m_size = other.m_size; + other.clear(); + return *this; + } + + template + ErrorOr HashSet::insert(const T& key) + { + return insert(move(T(key))); + } + + template + ErrorOr HashSet::insert(T&& key) + { + if (!empty() && get_bucket(key).contains(key)) + return {}; + + TRY(rebucket(m_size + 1)); + TRY(get_bucket(key).push_back(move(key))); + m_size++; + return {}; + } + + template + void HashSet::remove(const T& key) + { + if (empty()) return; + Vector& bucket = get_bucket(key); + for (size_type i = 0; i < bucket.size(); i++) + { + if (bucket[i] == key) + { + bucket.remove(i); + m_size--; + break; + } + } + } + + template + void HashSet::clear() + { + m_buckets.clear(); + m_size = 0; + } + + template + bool HashSet::contains(const T& key) const + { + if (empty()) return false; + return get_bucket(key).contains(key); + } + + template + typename HashSet::size_type HashSet::size() const + { + return m_size; + } + + template + bool HashSet::empty() const + { + return m_size == 0; + } + + template + ErrorOr HashSet::rebucket(size_type bucket_count) + { + if (m_buckets.size() >= bucket_count) + return {}; + + size_type new_bucket_count = BAN::Math::max(bucket_count, m_buckets.size() * 3 / 2); + Vector> new_buckets; + if (new_buckets.resize(new_bucket_count).is_error()) + return Error::from_string("HashSet: Could not allocate memory"); + + // NOTE: We have to copy the old keys to the new keys and not move + // since we might run out of memory half way through. + for (Vector& bucket : m_buckets) + { + for (T& key : bucket) + { + size_type bucket_index = HASH()(key) % new_buckets.size(); + if (new_buckets[bucket_index].push_back(key).is_error()) + return Error::from_string("HashSet: Could not allocate memory"); + } + } + + m_buckets = move(new_buckets); + return {}; + } + + template + Vector& HashSet::get_bucket(const T& key) + { + ASSERT(!m_buckets.empty()); + size_type index = HASH()(key) % m_buckets.size(); + return m_buckets[index]; + } + + template + const Vector& HashSet::get_bucket(const T& key) const + { + ASSERT(!m_buckets.empty()); + size_type index = HASH()(key) % m_buckets.size(); + return m_buckets[index]; + } + + + + template + HashSetIterator& HashSetIterator::operator++() + { + ASSERT(*this); + if (m_current_key == m_current_bucket->end()) + m_current_bucket++; + else + m_current_key++; + find_next(); + return *this; + } + + template + HashSetIterator HashSetIterator::operator++(int) + { + auto temp = *this; + ++(*this); + return temp; + } + + template + const T& HashSetIterator::operator*() const + { + ASSERT(m_owner && m_current_bucket && m_current_key); + return *m_current_key; + } + + template + const T* HashSetIterator::operator->() const + { + return &**this; + } + + template + bool HashSetIterator::operator==(const HashSetIterator& other) const + { + if (!m_owner || m_owner != other.m_owner) + return false; + return m_current_bucket == other.m_current_bucket + && m_current_key == other.m_current_key; + } + + template + bool HashSetIterator::operator!=(const HashSetIterator& other) const + { + return !(*this == other); + } + + template + HashSetIterator::HashSetIterator(const HashSet* owner, Vector>::const_iterator bucket) + : m_owner(owner) + , m_current_bucket(bucket) + { + if (m_current_bucket != m_owner->m_buckets.end()) + m_current_key = m_current_bucket->begin(); + find_next(); + } + + template + void HashSetIterator::find_next() + { + ASSERT(m_owner && m_current_bucket); + while (m_current_bucket != m_owner->m_buckets.end()) + { + if (m_current_key && m_current_key != m_current_bucket->end()) + return; + m_current_bucket++; + m_current_key = m_current_bucket->begin(); + } + m_current_key = typename Vector::const_iterator(); + } + +} \ No newline at end of file