#pragma once #include #include #include #include #include #include #include namespace BAN { template, bool STABLE = true> class HashSet { public: using value_type = T; using size_type = size_t; using iterator = IteratorDouble; using const_iterator = ConstIteratorDouble; public: HashSet() = default; HashSet(const HashSet&); HashSet(HashSet&&); HashSet& operator=(const HashSet&); HashSet& operator=(HashSet&&); ErrorOr insert(const T&); ErrorOr insert(T&&); void remove(const T&); void clear(); ErrorOr reserve(size_type); iterator begin() { return iterator(m_buckets.end(), m_buckets.begin()); } iterator end() { return iterator(m_buckets.end(), m_buckets.end()); } const_iterator begin() const { return const_iterator(m_buckets.end(), m_buckets.begin()); } const_iterator end() const { return const_iterator(m_buckets.end(), m_buckets.end()); } bool contains(const T&) const; size_type size() const; bool empty() const; private: ErrorOr rebucket(size_type); LinkedList& get_bucket(const T&); const LinkedList& get_bucket(const T&) const; private: Vector> m_buckets; size_type m_size = 0; }; 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; auto& bucket = get_bucket(key); for (auto it = bucket.begin(); it != bucket.end(); it++) { if (*it == key) { bucket.remove(it); m_size--; break; } } } template void HashSet::clear() { m_buckets.clear(); m_size = 0; } template ErrorOr HashSet::reserve(size_type size) { TRY(rebucket(size)); return {}; } 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 = Math::max(bucket_count, m_buckets.size() * 2); Vector> new_buckets; if (new_buckets.resize(new_bucket_count).is_error()) return Error::from_errno(ENOMEM); for (auto& bucket : m_buckets) { for (T& key : bucket) { size_type bucket_index = HASH()(key) % new_buckets.size(); if constexpr(STABLE) TRY(new_buckets[bucket_index].push_back(key)); else TRY(new_buckets[bucket_index].push_back(move(key))); } } m_buckets = move(new_buckets); return {}; } template LinkedList& HashSet::get_bucket(const T& key) { ASSERT(!m_buckets.empty()); size_type index = HASH()(key) % m_buckets.size(); return m_buckets[index]; } template const LinkedList& HashSet::get_bucket(const T& key) const { ASSERT(!m_buckets.empty()); size_type index = HASH()(key) % m_buckets.size(); return m_buckets[index]; } // Unstable hash set moves values between container during rebucketing. // This means that if insertion to set fails, elements could be in invalid state // and that container is no longer usable. This is better if either way you are // going to stop using the hash set after insertion fails. template> using HashSetUnstable = HashSet; }