#pragma once #include #include #include namespace BAN { template> class HashMap { public: using size_type = size_t; using key_type = Key; using value_type = T; public: HashMap() = default; HashMap(const HashMap&); HashMap(HashMap&&); ~HashMap(); HashMap& operator=(const HashMap&); HashMap& operator=(HashMap&&); ErrorOr insert(const Key&, const T&); ErrorOr insert(const Key&, T&&); template ErrorOr emplace(const Key&, Args&&...); ErrorOr reserve(size_type); void remove(const Key&); void clear(); T& operator[](const Key&); const T& operator[](const Key&) const; bool contains(const Key&) const; bool empty() const; size_type size() const; private: struct Entry { template Entry(const Key& key, Args&&... args) : key(key) , value(forward(args)...) {} Key key; T value; }; private: ErrorOr rebucket(size_type); LinkedList& get_bucket(const Key&); const LinkedList& get_bucket(const Key&) const; private: Vector> m_buckets; size_type m_size = 0; }; template HashMap::HashMap(const HashMap& other) { *this = other; } template HashMap::HashMap(HashMap&& other) { *this = move(other); } template HashMap::~HashMap() { clear(); } template HashMap& HashMap::operator=(const HashMap& other) { clear(); m_buckets = other.m_buckets; m_size = other.m_size; return *this; } template HashMap& HashMap::operator=(HashMap&& other) { clear(); m_buckets = move(other.m_buckets); m_size = other.m_size; other.m_size = 0; return *this; } template ErrorOr HashMap::insert(const Key& key, const T& value) { return insert(key, move(T(value))); } template ErrorOr HashMap::insert(const Key& key, T&& value) { return emplace(key, move(value)); } template template ErrorOr HashMap::emplace(const Key& key, Args&&... args) { ASSERT(!contains(key)); TRY(rebucket(m_size + 1)); auto& bucket = get_bucket(key); auto result = bucket.emplace_back(key, forward(args)...); if (result.is_error()) return Error::from_errno(ENOMEM); m_size++; return {}; } template ErrorOr HashMap::reserve(size_type size) { TRY(rebucket(size)); return {}; } template void HashMap::remove(const Key& key) { if (empty()) return; auto& bucket = get_bucket(key); for (auto it = bucket.begin(); it != bucket.end(); it++) { if (it->key == key) { bucket.remove(it); m_size--; return; } } } template void HashMap::clear() { m_buckets.clear(); m_size = 0; } template T& HashMap::operator[](const Key& key) { ASSERT(!empty()); auto& bucket = get_bucket(key); for (Entry& entry : bucket) if (entry.key == key) return entry.value; ASSERT(false); } template const T& HashMap::operator[](const Key& key) const { ASSERT(!empty()); const auto& bucket = get_bucket(key); for (const Entry& entry : bucket) if (entry.key == key) return entry.value; ASSERT(false); } template bool HashMap::contains(const Key& key) const { if (empty()) return false; const auto& bucket = get_bucket(key); for (const Entry& entry : bucket) if (entry.key == key) return true; return false; } template bool HashMap::empty() const { return m_size == 0; } template typename HashMap::size_type HashMap::size() const { return m_size; } template ErrorOr HashMap::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() * 2); Vector> new_buckets; if (new_buckets.resize(new_bucket_count).is_error()) return Error::from_errno(ENOMEM); // NOTE: We have to copy the old entries to the new entries and not move // since we might run out of memory half way through. for (auto& bucket : m_buckets) { for (Entry& entry : bucket) { size_type bucket_index = HASH()(entry.key) % new_buckets.size(); if (new_buckets[bucket_index].push_back(entry).is_error()) return Error::from_errno(ENOMEM); } } m_buckets = move(new_buckets); return {}; } template LinkedList::Entry>& HashMap::get_bucket(const Key& key) { ASSERT(!m_buckets.empty()); auto index = HASH()(key) % m_buckets.size(); return m_buckets[index]; } template const LinkedList::Entry>& HashMap::get_bucket(const Key& key) const { ASSERT(!m_buckets.empty()); auto index = HASH()(key) % m_buckets.size(); return m_buckets[index]; } }