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:
parent
3b21cc90ae
commit
3963afe343
|
@ -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 {};
|
||||
|
@ -226,15 +223,20 @@ namespace BAN
|
|||
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>;
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue