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
{
template<typename Container>
class HashMapIterator;
template<typename Key, typename T, typename HASH = BAN::hash<Key>>
template<typename Key, typename T, typename HASH = BAN::hash<Key>, bool STABLE = true>
class HashMap
{
public:
@ -35,12 +32,12 @@ namespace BAN
public:
HashMap() = default;
HashMap(const HashMap<Key, T, HASH>&);
HashMap(HashMap<Key, T, HASH>&&);
HashMap(const HashMap<Key, T, HASH, STABLE>&);
HashMap(HashMap<Key, T, HASH, STABLE>&&);
~HashMap();
HashMap<Key, T, HASH>& operator=(const HashMap<Key, T, HASH>&);
HashMap<Key, T, HASH>& operator=(HashMap<Key, T, HASH>&&);
HashMap<Key, T, HASH, STABLE>& operator=(const HashMap<Key, T, HASH, STABLE>&);
HashMap<Key, T, HASH, STABLE>& operator=(HashMap<Key, T, HASH, STABLE>&&);
ErrorOr<void> insert(const Key&, const T&);
ErrorOr<void> insert(const Key&, T&&);
@ -77,26 +74,26 @@ namespace BAN
friend iterator;
};
template<typename Key, typename T, typename HASH>
HashMap<Key, T, HASH>::HashMap(const HashMap<Key, T, HASH>& other)
template<typename Key, typename T, typename HASH, bool STABLE>
HashMap<Key, T, HASH, STABLE>::HashMap(const HashMap<Key, T, HASH, STABLE>& other)
{
*this = other;
}
template<typename Key, typename T, typename HASH>
HashMap<Key, T, HASH>::HashMap(HashMap<Key, T, HASH>&& other)
template<typename Key, typename T, typename HASH, bool STABLE>
HashMap<Key, T, HASH, STABLE>::HashMap(HashMap<Key, T, HASH, STABLE>&& other)
{
*this = move(other);
}
template<typename Key, typename T, typename HASH>
HashMap<Key, T, HASH>::~HashMap()
template<typename Key, typename T, typename HASH, bool STABLE>
HashMap<Key, T, HASH, STABLE>::~HashMap()
{
clear();
}
template<typename Key, typename T, typename HASH>
HashMap<Key, T, HASH>& HashMap<Key, T, HASH>::operator=(const HashMap<Key, T, HASH>& other)
template<typename Key, typename T, typename HASH, bool STABLE>
HashMap<Key, T, HASH, STABLE>& HashMap<Key, T, HASH, STABLE>::operator=(const HashMap<Key, T, HASH, STABLE>& other)
{
clear();
m_buckets = other.m_buckets;
@ -104,8 +101,8 @@ namespace BAN
return *this;
}
template<typename Key, typename T, typename HASH>
HashMap<Key, T, HASH>& HashMap<Key, T, HASH>::operator=(HashMap<Key, T, HASH>&& other)
template<typename Key, typename T, typename HASH, bool STABLE>
HashMap<Key, T, HASH, STABLE>& HashMap<Key, T, HASH, STABLE>::operator=(HashMap<Key, T, HASH, STABLE>&& other)
{
clear();
m_buckets = move(other.m_buckets);
@ -114,21 +111,21 @@ namespace BAN
return *this;
}
template<typename Key, typename T, typename HASH>
ErrorOr<void> HashMap<Key, T, HASH>::insert(const Key& key, const T& value)
template<typename Key, typename T, typename HASH, bool STABLE>
ErrorOr<void> HashMap<Key, T, HASH, STABLE>::insert(const Key& key, const T& value)
{
return insert(key, move(T(value)));
}
template<typename Key, typename T, typename HASH>
ErrorOr<void> HashMap<Key, T, HASH>::insert(const Key& key, T&& value)
template<typename Key, typename T, typename HASH, bool STABLE>
ErrorOr<void> HashMap<Key, T, HASH, STABLE>::insert(const Key& key, T&& 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>
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));
TRY(rebucket(m_size + 1));
@ -140,15 +137,15 @@ namespace BAN
return {};
}
template<typename Key, typename T, typename HASH>
ErrorOr<void> HashMap<Key, T, HASH>::reserve(size_type size)
template<typename Key, typename T, typename HASH, bool STABLE>
ErrorOr<void> HashMap<Key, T, HASH, STABLE>::reserve(size_type size)
{
TRY(rebucket(size));
return {};
}
template<typename Key, typename T, typename HASH>
void HashMap<Key, T, HASH>::remove(const Key& key)
template<typename Key, typename T, typename HASH, bool STABLE>
void HashMap<Key, T, HASH, STABLE>::remove(const Key& key)
{
if (empty()) return;
auto& bucket = get_bucket(key);
@ -163,15 +160,15 @@ namespace BAN
}
}
template<typename Key, typename T, typename HASH>
void HashMap<Key, T, HASH>::clear()
template<typename Key, typename T, typename HASH, bool STABLE>
void HashMap<Key, T, HASH, STABLE>::clear()
{
m_buckets.clear();
m_size = 0;
}
template<typename Key, typename T, typename HASH>
T& HashMap<Key, T, HASH>::operator[](const Key& key)
template<typename Key, typename T, typename HASH, bool STABLE>
T& HashMap<Key, T, HASH, STABLE>::operator[](const Key& key)
{
ASSERT(!empty());
auto& bucket = get_bucket(key);
@ -181,8 +178,8 @@ namespace BAN
ASSERT(false);
}
template<typename Key, typename T, typename HASH>
const T& HashMap<Key, T, HASH>::operator[](const Key& key) const
template<typename Key, typename T, typename HASH, bool STABLE>
const T& HashMap<Key, T, HASH, STABLE>::operator[](const Key& key) const
{
ASSERT(!empty());
const auto& bucket = get_bucket(key);
@ -192,8 +189,8 @@ namespace BAN
ASSERT(false);
}
template<typename Key, typename T, typename HASH>
bool HashMap<Key, T, HASH>::contains(const Key& key) const
template<typename Key, typename T, typename HASH, bool STABLE>
bool HashMap<Key, T, HASH, STABLE>::contains(const Key& key) const
{
if (empty()) return false;
const auto& bucket = get_bucket(key);
@ -203,20 +200,20 @@ namespace BAN
return false;
}
template<typename Key, typename T, typename HASH>
bool HashMap<Key, T, HASH>::empty() const
template<typename Key, typename T, typename HASH, bool STABLE>
bool HashMap<Key, T, HASH, STABLE>::empty() const
{
return m_size == 0;
}
template<typename Key, typename T, typename HASH>
typename HashMap<Key, T, HASH>::size_type HashMap<Key, T, HASH>::size() const
template<typename Key, typename T, typename HASH, bool STABLE>
typename HashMap<Key, T, HASH, STABLE>::size_type HashMap<Key, T, HASH, STABLE>::size() const
{
return m_size;
}
template<typename Key, typename T, typename HASH>
ErrorOr<void> HashMap<Key, T, HASH>::rebucket(size_type bucket_count)
template<typename Key, typename T, typename HASH, bool STABLE>
ErrorOr<void> HashMap<Key, T, HASH, STABLE>::rebucket(size_type bucket_count)
{
if (m_buckets.size() >= bucket_count)
return {};
@ -225,16 +222,21 @@ namespace BAN
Vector<LinkedList<Entry>> 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<typename Key, typename T, typename HASH>
LinkedList<typename HashMap<Key, T, HASH>::Entry>& HashMap<Key, T, HASH>::get_bucket(const Key& key)
template<typename Key, typename T, typename HASH, bool STABLE>
LinkedList<typename HashMap<Key, T, HASH, STABLE>::Entry>& HashMap<Key, T, HASH, STABLE>::get_bucket(const Key& key)
{
ASSERT(!m_buckets.empty());
auto index = HASH()(key) % m_buckets.size();
return m_buckets[index];
}
template<typename Key, typename T, typename HASH>
const LinkedList<typename HashMap<Key, T, HASH>::Entry>& HashMap<Key, T, HASH>::get_bucket(const Key& key) const
template<typename Key, typename T, typename HASH, bool STABLE>
const LinkedList<typename HashMap<Key, T, HASH, STABLE>::Entry>& HashMap<Key, T, HASH, STABLE>::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<typename Key, typename T, typename HASH = BAN::hash<Key>>
using HashMapUnstable = HashMap<Key, T, HASH, false>;
}