diff --git a/BAN/include/BAN/HashMap.h b/BAN/include/BAN/HashMap.h new file mode 100644 index 000000000..c0cce1874 --- /dev/null +++ b/BAN/include/BAN/HashMap.h @@ -0,0 +1,239 @@ +#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&&...); + + 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; + 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_string("HashMap: Could not allocate memory"); + m_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() * 3 / 2); + Vector> new_buckets; + if (new_buckets.resize(new_bucket_count).is_error()) + return Error::from_string("HashMap: Could not allocate memory"); + + // 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_string("HashMap: Could not allocate memory"); + } + } + + 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]; + } + +} \ No newline at end of file