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.
This commit is contained in:
Bananymous 2023-12-19 22:23:28 +02:00
parent 3b21cc90ae
commit 3963afe343
1 changed files with 63 additions and 54 deletions

View File

@ -7,10 +7,7 @@
namespace BAN namespace BAN
{ {
template<typename Container> template<typename Key, typename T, typename HASH = BAN::hash<Key>, bool STABLE = true>
class HashMapIterator;
template<typename Key, typename T, typename HASH = BAN::hash<Key>>
class HashMap class HashMap
{ {
public: public:
@ -35,12 +32,12 @@ namespace BAN
public: public:
HashMap() = default; HashMap() = default;
HashMap(const HashMap<Key, T, HASH>&); HashMap(const HashMap<Key, T, HASH, STABLE>&);
HashMap(HashMap<Key, T, HASH>&&); HashMap(HashMap<Key, T, HASH, STABLE>&&);
~HashMap(); ~HashMap();
HashMap<Key, T, HASH>& operator=(const HashMap<Key, T, HASH>&); HashMap<Key, T, HASH, STABLE>& operator=(const HashMap<Key, T, HASH, STABLE>&);
HashMap<Key, T, HASH>& operator=(HashMap<Key, T, HASH>&&); HashMap<Key, T, HASH, STABLE>& operator=(HashMap<Key, T, HASH, STABLE>&&);
ErrorOr<void> insert(const Key&, const T&); ErrorOr<void> insert(const Key&, const T&);
ErrorOr<void> insert(const Key&, T&&); ErrorOr<void> insert(const Key&, T&&);
@ -77,26 +74,26 @@ namespace BAN
friend iterator; friend iterator;
}; };
template<typename Key, typename T, typename HASH> template<typename Key, typename T, typename HASH, bool STABLE>
HashMap<Key, T, HASH>::HashMap(const HashMap<Key, T, HASH>& other) HashMap<Key, T, HASH, STABLE>::HashMap(const HashMap<Key, T, HASH, STABLE>& other)
{ {
*this = other; *this = other;
} }
template<typename Key, typename T, typename HASH> template<typename Key, typename T, typename HASH, bool STABLE>
HashMap<Key, T, HASH>::HashMap(HashMap<Key, T, HASH>&& other) HashMap<Key, T, HASH, STABLE>::HashMap(HashMap<Key, T, HASH, STABLE>&& other)
{ {
*this = move(other); *this = move(other);
} }
template<typename Key, typename T, typename HASH> template<typename Key, typename T, typename HASH, bool STABLE>
HashMap<Key, T, HASH>::~HashMap() HashMap<Key, T, HASH, STABLE>::~HashMap()
{ {
clear(); clear();
} }
template<typename Key, typename T, typename HASH> template<typename Key, typename T, typename HASH, bool STABLE>
HashMap<Key, T, HASH>& HashMap<Key, T, HASH>::operator=(const HashMap<Key, T, HASH>& other) HashMap<Key, T, HASH, STABLE>& HashMap<Key, T, HASH, STABLE>::operator=(const HashMap<Key, T, HASH, STABLE>& other)
{ {
clear(); clear();
m_buckets = other.m_buckets; m_buckets = other.m_buckets;
@ -104,8 +101,8 @@ namespace BAN
return *this; return *this;
} }
template<typename Key, typename T, typename HASH> template<typename Key, typename T, typename HASH, bool STABLE>
HashMap<Key, T, HASH>& HashMap<Key, T, HASH>::operator=(HashMap<Key, T, HASH>&& other) HashMap<Key, T, HASH, STABLE>& HashMap<Key, T, HASH, STABLE>::operator=(HashMap<Key, T, HASH, STABLE>&& other)
{ {
clear(); clear();
m_buckets = move(other.m_buckets); m_buckets = move(other.m_buckets);
@ -114,21 +111,21 @@ namespace BAN
return *this; return *this;
} }
template<typename Key, typename T, typename HASH> template<typename Key, typename T, typename HASH, bool STABLE>
ErrorOr<void> HashMap<Key, T, HASH>::insert(const Key& key, const T& value) ErrorOr<void> HashMap<Key, T, HASH, STABLE>::insert(const Key& key, const T& value)
{ {
return insert(key, move(T(value))); return insert(key, move(T(value)));
} }
template<typename Key, typename T, typename HASH> template<typename Key, typename T, typename HASH, bool STABLE>
ErrorOr<void> HashMap<Key, T, HASH>::insert(const Key& key, T&& value) ErrorOr<void> HashMap<Key, T, HASH, STABLE>::insert(const Key& key, T&& value)
{ {
return emplace(key, move(value)); return emplace(key, move(value));
} }
template<typename Key, typename T, typename HASH> template<typename Key, typename T, typename HASH, bool STABLE>
template<typename... Args> template<typename... Args>
ErrorOr<void> HashMap<Key, T, HASH>::emplace(const Key& key, Args&&... args) ErrorOr<void> HashMap<Key, T, HASH, STABLE>::emplace(const Key& key, Args&&... args)
{ {
ASSERT(!contains(key)); ASSERT(!contains(key));
TRY(rebucket(m_size + 1)); TRY(rebucket(m_size + 1));
@ -140,15 +137,15 @@ namespace BAN
return {}; return {};
} }
template<typename Key, typename T, typename HASH> template<typename Key, typename T, typename HASH, bool STABLE>
ErrorOr<void> HashMap<Key, T, HASH>::reserve(size_type size) ErrorOr<void> HashMap<Key, T, HASH, STABLE>::reserve(size_type size)
{ {
TRY(rebucket(size)); TRY(rebucket(size));
return {}; return {};
} }
template<typename Key, typename T, typename HASH> template<typename Key, typename T, typename HASH, bool STABLE>
void HashMap<Key, T, HASH>::remove(const Key& key) void HashMap<Key, T, HASH, STABLE>::remove(const Key& key)
{ {
if (empty()) return; if (empty()) return;
auto& bucket = get_bucket(key); auto& bucket = get_bucket(key);
@ -163,15 +160,15 @@ namespace BAN
} }
} }
template<typename Key, typename T, typename HASH> template<typename Key, typename T, typename HASH, bool STABLE>
void HashMap<Key, T, HASH>::clear() void HashMap<Key, T, HASH, STABLE>::clear()
{ {
m_buckets.clear(); m_buckets.clear();
m_size = 0; m_size = 0;
} }
template<typename Key, typename T, typename HASH> template<typename Key, typename T, typename HASH, bool STABLE>
T& HashMap<Key, T, HASH>::operator[](const Key& key) T& HashMap<Key, T, HASH, STABLE>::operator[](const Key& key)
{ {
ASSERT(!empty()); ASSERT(!empty());
auto& bucket = get_bucket(key); auto& bucket = get_bucket(key);
@ -181,8 +178,8 @@ namespace BAN
ASSERT(false); ASSERT(false);
} }
template<typename Key, typename T, typename HASH> template<typename Key, typename T, typename HASH, bool STABLE>
const T& HashMap<Key, T, HASH>::operator[](const Key& key) const const T& HashMap<Key, T, HASH, STABLE>::operator[](const Key& key) const
{ {
ASSERT(!empty()); ASSERT(!empty());
const auto& bucket = get_bucket(key); const auto& bucket = get_bucket(key);
@ -192,8 +189,8 @@ namespace BAN
ASSERT(false); ASSERT(false);
} }
template<typename Key, typename T, typename HASH> template<typename Key, typename T, typename HASH, bool STABLE>
bool HashMap<Key, T, HASH>::contains(const Key& key) const bool HashMap<Key, T, HASH, STABLE>::contains(const Key& key) const
{ {
if (empty()) return false; if (empty()) return false;
const auto& bucket = get_bucket(key); const auto& bucket = get_bucket(key);
@ -203,20 +200,20 @@ namespace BAN
return false; return false;
} }
template<typename Key, typename T, typename HASH> template<typename Key, typename T, typename HASH, bool STABLE>
bool HashMap<Key, T, HASH>::empty() const bool HashMap<Key, T, HASH, STABLE>::empty() const
{ {
return m_size == 0; return m_size == 0;
} }
template<typename Key, typename T, typename HASH> template<typename Key, typename T, typename HASH, bool STABLE>
typename HashMap<Key, T, HASH>::size_type HashMap<Key, T, HASH>::size() const typename HashMap<Key, T, HASH, STABLE>::size_type HashMap<Key, T, HASH, STABLE>::size() const
{ {
return m_size; return m_size;
} }
template<typename Key, typename T, typename HASH> template<typename Key, typename T, typename HASH, bool STABLE>
ErrorOr<void> HashMap<Key, T, HASH>::rebucket(size_type bucket_count) ErrorOr<void> HashMap<Key, T, HASH, STABLE>::rebucket(size_type bucket_count)
{ {
if (m_buckets.size() >= bucket_count) if (m_buckets.size() >= bucket_count)
return {}; return {};
@ -226,15 +223,20 @@ namespace BAN
if (new_buckets.resize(new_bucket_count).is_error()) if (new_buckets.resize(new_bucket_count).is_error())
return Error::from_errno(ENOMEM); return Error::from_errno(ENOMEM);
// NOTE: We have to copy the old entries to the new entries and not move if constexpr(STABLE)
// since we might run out of memory half way through.
for (auto& bucket : m_buckets)
{ {
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(); for (Entry& entry : bucket)
if (new_buckets[bucket_index].push_back(entry).is_error()) {
return Error::from_errno(ENOMEM); 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 {}; return {};
} }
template<typename Key, typename T, typename HASH> template<typename Key, typename T, typename HASH, bool STABLE>
LinkedList<typename HashMap<Key, T, HASH>::Entry>& HashMap<Key, T, HASH>::get_bucket(const Key& key) LinkedList<typename HashMap<Key, T, HASH, STABLE>::Entry>& HashMap<Key, T, HASH, STABLE>::get_bucket(const Key& key)
{ {
ASSERT(!m_buckets.empty()); ASSERT(!m_buckets.empty());
auto index = HASH()(key) % m_buckets.size(); auto index = HASH()(key) % m_buckets.size();
return m_buckets[index]; return m_buckets[index];
} }
template<typename Key, typename T, typename HASH> template<typename Key, typename T, typename HASH, bool STABLE>
const LinkedList<typename HashMap<Key, T, HASH>::Entry>& HashMap<Key, T, HASH>::get_bucket(const Key& key) const const LinkedList<typename HashMap<Key, T, HASH, STABLE>::Entry>& HashMap<Key, T, HASH, STABLE>::get_bucket(const Key& key) const
{ {
ASSERT(!m_buckets.empty()); ASSERT(!m_buckets.empty());
auto index = HASH()(key) % m_buckets.size(); auto index = HASH()(key) % m_buckets.size();
return m_buckets[index]; 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<typename Key, typename T, typename HASH = BAN::hash<Key>>
using HashMapUnstable = HashMap<Key, T, HASH, false>;
} }