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
|
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,6 +223,8 @@ 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);
|
||||||
|
|
||||||
|
if constexpr(STABLE)
|
||||||
|
{
|
||||||
// NOTE: We have to copy the old entries to the new entries and not move
|
// 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.
|
// since we might run out of memory half way through.
|
||||||
for (auto& bucket : m_buckets)
|
for (auto& bucket : m_buckets)
|
||||||
|
@ -233,8 +232,11 @@ namespace BAN
|
||||||
for (Entry& entry : bucket)
|
for (Entry& entry : bucket)
|
||||||
{
|
{
|
||||||
size_type bucket_index = HASH()(entry.key) % new_buckets.size();
|
size_type bucket_index = HASH()(entry.key) % new_buckets.size();
|
||||||
if (new_buckets[bucket_index].push_back(entry).is_error())
|
if constexpr(STABLE)
|
||||||
return Error::from_errno(ENOMEM);
|
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>;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue