From 3963afe3434fa1b766d0c314d6c8d7dff8459b48 Mon Sep 17 00:00:00 2001 From: Bananymous Date: Tue, 19 Dec 2023 22:23:28 +0200 Subject: [PATCH] BAN: Add unstable version of hash map This version differs only when doing rebucket. If rebucket fails, the whole hash map is invalidated. This allows rebucketing to use moving instead of copying. --- BAN/include/BAN/HashMap.h | 117 ++++++++++++++++++++------------------ 1 file changed, 63 insertions(+), 54 deletions(-) diff --git a/BAN/include/BAN/HashMap.h b/BAN/include/BAN/HashMap.h index 38b3afba..061a73e9 100644 --- a/BAN/include/BAN/HashMap.h +++ b/BAN/include/BAN/HashMap.h @@ -7,10 +7,7 @@ namespace BAN { - template - class HashMapIterator; - - template> + template, bool STABLE = true> class HashMap { public: @@ -35,12 +32,12 @@ namespace BAN public: HashMap() = default; - HashMap(const HashMap&); - HashMap(HashMap&&); + HashMap(const HashMap&); + HashMap(HashMap&&); ~HashMap(); - HashMap& operator=(const HashMap&); - HashMap& operator=(HashMap&&); + HashMap& operator=(const HashMap&); + HashMap& operator=(HashMap&&); ErrorOr insert(const Key&, const T&); ErrorOr insert(const Key&, T&&); @@ -77,26 +74,26 @@ namespace BAN friend iterator; }; - template - HashMap::HashMap(const HashMap& other) + template + HashMap::HashMap(const HashMap& other) { *this = other; } - template - HashMap::HashMap(HashMap&& other) + template + HashMap::HashMap(HashMap&& other) { *this = move(other); } - template - HashMap::~HashMap() + template + HashMap::~HashMap() { clear(); } - template - HashMap& HashMap::operator=(const HashMap& other) + template + HashMap& HashMap::operator=(const HashMap& other) { clear(); m_buckets = other.m_buckets; @@ -104,8 +101,8 @@ namespace BAN return *this; } - template - HashMap& HashMap::operator=(HashMap&& other) + template + HashMap& HashMap::operator=(HashMap&& other) { clear(); m_buckets = move(other.m_buckets); @@ -114,21 +111,21 @@ namespace BAN return *this; } - template - ErrorOr HashMap::insert(const Key& key, const T& value) + 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) + template + ErrorOr HashMap::insert(const Key& key, T&& value) { return emplace(key, move(value)); } - template + template template - ErrorOr HashMap::emplace(const Key& key, Args&&... args) + ErrorOr HashMap::emplace(const Key& key, Args&&... args) { ASSERT(!contains(key)); TRY(rebucket(m_size + 1)); @@ -140,15 +137,15 @@ namespace BAN return {}; } - template - ErrorOr HashMap::reserve(size_type size) + template + ErrorOr HashMap::reserve(size_type size) { TRY(rebucket(size)); return {}; } - template - void HashMap::remove(const Key& key) + template + void HashMap::remove(const Key& key) { if (empty()) return; auto& bucket = get_bucket(key); @@ -163,15 +160,15 @@ namespace BAN } } - template - void HashMap::clear() + template + void HashMap::clear() { m_buckets.clear(); m_size = 0; } - template - T& HashMap::operator[](const Key& key) + template + T& HashMap::operator[](const Key& key) { ASSERT(!empty()); auto& bucket = get_bucket(key); @@ -181,8 +178,8 @@ namespace BAN ASSERT(false); } - template - const T& HashMap::operator[](const Key& key) const + template + const T& HashMap::operator[](const Key& key) const { ASSERT(!empty()); const auto& bucket = get_bucket(key); @@ -192,8 +189,8 @@ namespace BAN ASSERT(false); } - template - bool HashMap::contains(const Key& key) const + template + bool HashMap::contains(const Key& key) const { if (empty()) return false; const auto& bucket = get_bucket(key); @@ -203,20 +200,20 @@ namespace BAN return false; } - template - bool HashMap::empty() const + template + bool HashMap::empty() const { return m_size == 0; } - template - typename HashMap::size_type HashMap::size() const + template + typename HashMap::size_type HashMap::size() const { return m_size; } - template - ErrorOr HashMap::rebucket(size_type bucket_count) + template + ErrorOr HashMap::rebucket(size_type bucket_count) { if (m_buckets.size() >= bucket_count) return {}; @@ -225,16 +222,21 @@ namespace BAN 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) + + if constexpr(STABLE) { - for (Entry& entry : bucket) + // 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) { - 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); + for (Entry& entry : bucket) + { + size_type bucket_index = HASH()(entry.key) % new_buckets.size(); + if constexpr(STABLE) + TRY(new_buckets[bucket_index].push_back(entry)); + else + TRY(new_buckets[bucket_index].push_back(BAN::move(entry))); + } } } @@ -242,20 +244,27 @@ namespace BAN return {}; } - template - LinkedList::Entry>& HashMap::get_bucket(const Key& key) + 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 + 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]; } + // Unstable hash map moves values between container during rebucketing. + // This means that if insertion to map 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 map after insertion fails. + template> + using HashMapUnstable = HashMap; + }