BAN: Cleanup HashSet
I now use BAN::Iterator as the base iterator. Also unstable version was added.
This commit is contained in:
parent
bacc0db778
commit
79a15132da
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include <BAN/Errors.h>
|
#include <BAN/Errors.h>
|
||||||
#include <BAN/Hash.h>
|
#include <BAN/Hash.h>
|
||||||
|
#include <BAN/Iterators.h>
|
||||||
#include <BAN/Math.h>
|
#include <BAN/Math.h>
|
||||||
#include <BAN/Move.h>
|
#include <BAN/Move.h>
|
||||||
#include <BAN/Vector.h>
|
#include <BAN/Vector.h>
|
||||||
|
@ -9,24 +10,22 @@
|
||||||
namespace BAN
|
namespace BAN
|
||||||
{
|
{
|
||||||
|
|
||||||
template<typename T, typename HASH>
|
template<typename T, typename HASH = hash<T>, bool STABLE = true>
|
||||||
class HashSetIterator;
|
|
||||||
|
|
||||||
template<typename T, typename HASH = hash<T>>
|
|
||||||
class HashSet
|
class HashSet
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using value_type = T;
|
using value_type = T;
|
||||||
using size_type = hash_t;
|
using size_type = size_t;
|
||||||
using const_iterator = HashSetIterator<T, HASH>;
|
using iterator = IteratorDouble<T, Vector, Vector, HashSet>;
|
||||||
|
using const_iterator = ConstIteratorDouble<T, Vector, Vector, HashSet>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
HashSet() = default;
|
HashSet() = default;
|
||||||
HashSet(const HashSet<T, HASH>&);
|
HashSet(const HashSet&);
|
||||||
HashSet(HashSet<T, HASH>&&);
|
HashSet(HashSet&&);
|
||||||
|
|
||||||
HashSet<T, HASH>& operator=(const HashSet<T, HASH>&);
|
HashSet& operator=(const HashSet&);
|
||||||
HashSet<T, HASH>& operator=(HashSet<T, HASH>&&);
|
HashSet& operator=(HashSet&&);
|
||||||
|
|
||||||
ErrorOr<void> insert(const T&);
|
ErrorOr<void> insert(const T&);
|
||||||
ErrorOr<void> insert(T&&);
|
ErrorOr<void> insert(T&&);
|
||||||
|
@ -35,8 +34,10 @@ namespace BAN
|
||||||
|
|
||||||
ErrorOr<void> reserve(size_type);
|
ErrorOr<void> reserve(size_type);
|
||||||
|
|
||||||
const_iterator begin() const { return const_iterator(this, m_buckets.begin()); }
|
iterator begin() { return iterator(m_buckets.end(), m_buckets.begin()); }
|
||||||
const_iterator end() const { return const_iterator(this, m_buckets.end()); }
|
iterator end() { return iterator(m_buckets.end(), m_buckets.end()); }
|
||||||
|
const_iterator begin() const { return const_iterator(m_buckets.end(), m_buckets.begin()); }
|
||||||
|
const_iterator end() const { return const_iterator(m_buckets.end(), m_buckets.end()); }
|
||||||
|
|
||||||
bool contains(const T&) const;
|
bool contains(const T&) const;
|
||||||
|
|
||||||
|
@ -51,57 +52,25 @@ namespace BAN
|
||||||
private:
|
private:
|
||||||
Vector<Vector<T>> m_buckets;
|
Vector<Vector<T>> m_buckets;
|
||||||
size_type m_size = 0;
|
size_type m_size = 0;
|
||||||
|
|
||||||
friend class HashSetIterator<T, HASH>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename T, typename HASH>
|
template<typename T, typename HASH, bool STABLE>
|
||||||
class HashSetIterator
|
HashSet<T, HASH, STABLE>::HashSet(const HashSet& other)
|
||||||
{
|
|
||||||
public:
|
|
||||||
HashSetIterator() = default;
|
|
||||||
HashSetIterator(const HashSetIterator<T, HASH>&);
|
|
||||||
|
|
||||||
HashSetIterator<T, HASH>& operator++();
|
|
||||||
HashSetIterator<T, HASH> operator++(int);
|
|
||||||
|
|
||||||
const T& operator*() const;
|
|
||||||
const T* operator->() const;
|
|
||||||
|
|
||||||
bool operator==(const HashSetIterator<T, HASH>&) const;
|
|
||||||
bool operator!=(const HashSetIterator<T, HASH>&) const;
|
|
||||||
|
|
||||||
operator bool() const { return m_owner && m_current_bucket; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
HashSetIterator(const HashSet<T, HASH>* owner, Vector<Vector<T>>::const_iterator bucket);
|
|
||||||
void find_next();
|
|
||||||
|
|
||||||
private:
|
|
||||||
const HashSet<T, HASH>* m_owner = nullptr;
|
|
||||||
Vector<Vector<T>>::const_iterator m_current_bucket;
|
|
||||||
Vector<T>::const_iterator m_current_key;
|
|
||||||
|
|
||||||
friend class HashSet<T, HASH>;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename T, typename HASH>
|
|
||||||
HashSet<T, HASH>::HashSet(const HashSet<T, HASH>& other)
|
|
||||||
: m_buckets(other.m_buckets)
|
: m_buckets(other.m_buckets)
|
||||||
, m_size(other.m_size)
|
, m_size(other.m_size)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T, typename HASH>
|
template<typename T, typename HASH, bool STABLE>
|
||||||
HashSet<T, HASH>::HashSet(HashSet<T, HASH>&& other)
|
HashSet<T, HASH, STABLE>::HashSet(HashSet&& other)
|
||||||
: m_buckets(move(other.m_buckets))
|
: m_buckets(move(other.m_buckets))
|
||||||
, m_size(other.m_size)
|
, m_size(other.m_size)
|
||||||
{
|
{
|
||||||
other.clear();
|
other.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T, typename HASH>
|
template<typename T, typename HASH, bool STABLE>
|
||||||
HashSet<T, HASH>& HashSet<T, HASH>::operator=(const HashSet<T, HASH>& other)
|
HashSet<T, HASH, STABLE>& HashSet<T, HASH, STABLE>::operator=(const HashSet& other)
|
||||||
{
|
{
|
||||||
clear();
|
clear();
|
||||||
m_buckets = other.m_buckets;
|
m_buckets = other.m_buckets;
|
||||||
|
@ -109,8 +78,8 @@ namespace BAN
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T, typename HASH>
|
template<typename T, typename HASH, bool STABLE>
|
||||||
HashSet<T, HASH>& HashSet<T, HASH>::operator=(HashSet<T, HASH>&& other)
|
HashSet<T, HASH, STABLE>& HashSet<T, HASH, STABLE>::operator=(HashSet&& other)
|
||||||
{
|
{
|
||||||
clear();
|
clear();
|
||||||
m_buckets = move(other.m_buckets);
|
m_buckets = move(other.m_buckets);
|
||||||
|
@ -119,14 +88,14 @@ namespace BAN
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T, typename HASH>
|
template<typename T, typename HASH, bool STABLE>
|
||||||
ErrorOr<void> HashSet<T, HASH>::insert(const T& key)
|
ErrorOr<void> HashSet<T, HASH, STABLE>::insert(const T& key)
|
||||||
{
|
{
|
||||||
return insert(move(T(key)));
|
return insert(move(T(key)));
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T, typename HASH>
|
template<typename T, typename HASH, bool STABLE>
|
||||||
ErrorOr<void> HashSet<T, HASH>::insert(T&& key)
|
ErrorOr<void> HashSet<T, HASH, STABLE>::insert(T&& key)
|
||||||
{
|
{
|
||||||
if (!empty() && get_bucket(key).contains(key))
|
if (!empty() && get_bucket(key).contains(key))
|
||||||
return {};
|
return {};
|
||||||
|
@ -137,8 +106,8 @@ namespace BAN
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T, typename HASH>
|
template<typename T, typename HASH, bool STABLE>
|
||||||
void HashSet<T, HASH>::remove(const T& key)
|
void HashSet<T, HASH, STABLE>::remove(const T& key)
|
||||||
{
|
{
|
||||||
if (empty()) return;
|
if (empty()) return;
|
||||||
Vector<T>& bucket = get_bucket(key);
|
Vector<T>& bucket = get_bucket(key);
|
||||||
|
@ -153,41 +122,41 @@ namespace BAN
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T, typename HASH>
|
template<typename T, typename HASH, bool STABLE>
|
||||||
void HashSet<T, HASH>::clear()
|
void HashSet<T, HASH, STABLE>::clear()
|
||||||
{
|
{
|
||||||
m_buckets.clear();
|
m_buckets.clear();
|
||||||
m_size = 0;
|
m_size = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T, typename HASH>
|
template<typename T, typename HASH, bool STABLE>
|
||||||
ErrorOr<void> HashSet<T, HASH>::reserve(size_type size)
|
ErrorOr<void> HashSet<T, HASH, STABLE>::reserve(size_type size)
|
||||||
{
|
{
|
||||||
TRY(rebucket(size));
|
TRY(rebucket(size));
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T, typename HASH>
|
template<typename T, typename HASH, bool STABLE>
|
||||||
bool HashSet<T, HASH>::contains(const T& key) const
|
bool HashSet<T, HASH, STABLE>::contains(const T& key) const
|
||||||
{
|
{
|
||||||
if (empty()) return false;
|
if (empty()) return false;
|
||||||
return get_bucket(key).contains(key);
|
return get_bucket(key).contains(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T, typename HASH>
|
template<typename T, typename HASH, bool STABLE>
|
||||||
typename HashSet<T, HASH>::size_type HashSet<T, HASH>::size() const
|
typename HashSet<T, HASH, STABLE>::size_type HashSet<T, HASH, STABLE>::size() const
|
||||||
{
|
{
|
||||||
return m_size;
|
return m_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T, typename HASH>
|
template<typename T, typename HASH, bool STABLE>
|
||||||
bool HashSet<T, HASH>::empty() const
|
bool HashSet<T, HASH, STABLE>::empty() const
|
||||||
{
|
{
|
||||||
return m_size == 0;
|
return m_size == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T, typename HASH>
|
template<typename T, typename HASH, bool STABLE>
|
||||||
ErrorOr<void> HashSet<T, HASH>::rebucket(size_type bucket_count)
|
ErrorOr<void> HashSet<T, HASH, STABLE>::rebucket(size_type bucket_count)
|
||||||
{
|
{
|
||||||
if (m_buckets.size() >= bucket_count)
|
if (m_buckets.size() >= bucket_count)
|
||||||
return {};
|
return {};
|
||||||
|
@ -197,15 +166,15 @@ 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 keys to the new keys and not move
|
for (auto& bucket : m_buckets)
|
||||||
// since we might run out of memory half way through.
|
|
||||||
for (Vector<T>& bucket : m_buckets)
|
|
||||||
{
|
{
|
||||||
for (T& key : bucket)
|
for (T& key : bucket)
|
||||||
{
|
{
|
||||||
size_type bucket_index = HASH()(key) % new_buckets.size();
|
size_type bucket_index = HASH()(key) % new_buckets.size();
|
||||||
if (new_buckets[bucket_index].push_back(key).is_error())
|
if constexpr(STABLE)
|
||||||
return Error::from_errno(ENOMEM);
|
TRY(new_buckets[bucket_index].push_back(key));
|
||||||
|
else
|
||||||
|
TRY(new_buckets[bucket_index].push_back(move(key)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,92 +182,27 @@ namespace BAN
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T, typename HASH>
|
template<typename T, typename HASH, bool STABLE>
|
||||||
Vector<T>& HashSet<T, HASH>::get_bucket(const T& key)
|
Vector<T>& HashSet<T, HASH, STABLE>::get_bucket(const T& key)
|
||||||
{
|
{
|
||||||
ASSERT(!m_buckets.empty());
|
ASSERT(!m_buckets.empty());
|
||||||
size_type index = HASH()(key) % m_buckets.size();
|
size_type index = HASH()(key) % m_buckets.size();
|
||||||
return m_buckets[index];
|
return m_buckets[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T, typename HASH>
|
template<typename T, typename HASH, bool STABLE>
|
||||||
const Vector<T>& HashSet<T, HASH>::get_bucket(const T& key) const
|
const Vector<T>& HashSet<T, HASH, STABLE>::get_bucket(const T& key) const
|
||||||
{
|
{
|
||||||
ASSERT(!m_buckets.empty());
|
ASSERT(!m_buckets.empty());
|
||||||
size_type index = HASH()(key) % m_buckets.size();
|
size_type index = HASH()(key) % m_buckets.size();
|
||||||
return m_buckets[index];
|
return m_buckets[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T, typename HASH>
|
// Unstable hash set moves values between container during rebucketing.
|
||||||
HashSetIterator<T, HASH>& HashSetIterator<T, HASH>::operator++()
|
// This means that if insertion to set fails, elements could be in invalid state
|
||||||
{
|
// and that container is no longer usable. This is better if either way you are
|
||||||
ASSERT(*this);
|
// going to stop using the hash set after insertion fails.
|
||||||
if (m_current_key == m_current_bucket->end())
|
template<typename T, typename HASH = BAN::hash<T>>
|
||||||
m_current_bucket++;
|
using HashSetUnstable = HashSet<T, HASH, false>;
|
||||||
else
|
|
||||||
m_current_key++;
|
|
||||||
find_next();
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T, typename HASH>
|
|
||||||
HashSetIterator<T, HASH> HashSetIterator<T, HASH>::operator++(int)
|
|
||||||
{
|
|
||||||
auto temp = *this;
|
|
||||||
++(*this);
|
|
||||||
return temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T, typename HASH>
|
|
||||||
const T& HashSetIterator<T, HASH>::operator*() const
|
|
||||||
{
|
|
||||||
ASSERT(m_owner && m_current_bucket && m_current_key);
|
|
||||||
return *m_current_key;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T, typename HASH>
|
|
||||||
const T* HashSetIterator<T, HASH>::operator->() const
|
|
||||||
{
|
|
||||||
return &**this;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T, typename HASH>
|
|
||||||
bool HashSetIterator<T, HASH>::operator==(const HashSetIterator<T, HASH>& other) const
|
|
||||||
{
|
|
||||||
if (!m_owner || m_owner != other.m_owner)
|
|
||||||
return false;
|
|
||||||
return m_current_bucket == other.m_current_bucket
|
|
||||||
&& m_current_key == other.m_current_key;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T, typename HASH>
|
|
||||||
bool HashSetIterator<T, HASH>::operator!=(const HashSetIterator<T, HASH>& other) const
|
|
||||||
{
|
|
||||||
return !(*this == other);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T, typename HASH>
|
|
||||||
HashSetIterator<T, HASH>::HashSetIterator(const HashSet<T, HASH>* owner, Vector<Vector<T>>::const_iterator bucket)
|
|
||||||
: m_owner(owner)
|
|
||||||
, m_current_bucket(bucket)
|
|
||||||
{
|
|
||||||
if (m_current_bucket != m_owner->m_buckets.end())
|
|
||||||
m_current_key = m_current_bucket->begin();
|
|
||||||
find_next();
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T, typename HASH>
|
|
||||||
void HashSetIterator<T, HASH>::find_next()
|
|
||||||
{
|
|
||||||
ASSERT(m_owner && m_current_bucket);
|
|
||||||
while (m_current_bucket != m_owner->m_buckets.end())
|
|
||||||
{
|
|
||||||
if (m_current_key && m_current_key != m_current_bucket->end())
|
|
||||||
return;
|
|
||||||
m_current_bucket++;
|
|
||||||
m_current_key = m_current_bucket->begin();
|
|
||||||
}
|
|
||||||
m_current_key = typename Vector<T>::const_iterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue