328 lines
7.4 KiB
C++
328 lines
7.4 KiB
C++
// =====================================================================================================================
|
|
// fennec, a free and open source game engine
|
|
// Copyright © 2025 Medusa Slockbower
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
// =====================================================================================================================
|
|
|
|
#ifndef FENNEC_CONTAINERS_SET_H
|
|
#define FENNEC_CONTAINERS_SET_H
|
|
|
|
// https://programming.guide/robin-hood-hashing.html
|
|
|
|
#include <fennec/containers/optional.h>
|
|
#include <fennec/lang/compare.h>
|
|
#include <fennec/math/ext/primes.h>
|
|
#include <fennec/memory/allocator.h>
|
|
#include <fennec/lang/hashing.h>
|
|
|
|
namespace fennec
|
|
{
|
|
|
|
// TODO: Document
|
|
|
|
template<typename T, class Hash = hash<T>, class Equals = equality<T>, class Alloc = allocator<T>>
|
|
struct set {
|
|
public:
|
|
using alloc_t = typename allocator_traits<Alloc>::template rebind<T>;
|
|
using hash_t = Hash;
|
|
using equal_t = Equals;
|
|
using elem_t = T;
|
|
|
|
class iterator;
|
|
static constexpr size_t npos = -1;
|
|
static constexpr double default_load = 0.8;
|
|
|
|
private:
|
|
struct node {
|
|
optional<elem_t> value;
|
|
int psl;
|
|
|
|
constexpr node() = default;
|
|
constexpr ~node() = default;
|
|
};
|
|
|
|
public:
|
|
constexpr set()
|
|
: _alloc()
|
|
, _hash()
|
|
, _size(0)
|
|
, _load(default_load) {
|
|
};
|
|
|
|
constexpr set(const hash_t& hash)
|
|
: _alloc()
|
|
, _hash(hash)
|
|
, _size(0)
|
|
, _load(default_load) {
|
|
}
|
|
|
|
constexpr set(hash_t&& hash) noexcept
|
|
: _alloc()
|
|
, _hash(hash)
|
|
, _size(0)
|
|
, _load(default_load) {
|
|
}
|
|
|
|
constexpr set(const alloc_t& alloc)
|
|
: _alloc(alloc)
|
|
, _hash()
|
|
, _size(0)
|
|
, _load(default_load) {
|
|
}
|
|
|
|
constexpr set(alloc_t&& alloc) noexcept
|
|
: _alloc(alloc)
|
|
, _hash()
|
|
, _size(0)
|
|
, _load(default_load) {
|
|
}
|
|
|
|
constexpr set(const hash_t& hash, const alloc_t& alloc)
|
|
: _alloc(alloc)
|
|
, _hash(hash)
|
|
, _size(0)
|
|
, _load(default_load) {
|
|
}
|
|
|
|
constexpr set(const hash_t& hash, alloc_t&& alloc) noexcept
|
|
: _alloc(alloc)
|
|
, _hash(hash)
|
|
, _size(0)
|
|
, _load(default_load) {
|
|
}
|
|
|
|
constexpr set(hash_t&& hash, alloc_t&& alloc) noexcept
|
|
: _alloc(alloc)
|
|
, _hash(hash)
|
|
, _size(0)
|
|
, _load(default_load) {
|
|
}
|
|
|
|
constexpr set(hash_t&& hash, const alloc_t& alloc) noexcept
|
|
: _alloc(alloc)
|
|
, _hash(hash)
|
|
, _size(0)
|
|
, _load(default_load) {
|
|
}
|
|
|
|
constexpr set(const set& set)
|
|
: _alloc(set._alloc)
|
|
, _hash(set._hash)
|
|
, _size(set._size)
|
|
, _load(default_load) {
|
|
}
|
|
|
|
constexpr set(set&& set) noexcept
|
|
: _alloc(fennec::move(set._alloc))
|
|
, _hash(fennec::move(set._hash))
|
|
, _size(fennec::move(set._size)) {
|
|
}
|
|
|
|
constexpr ~set() = default;
|
|
|
|
constexpr size_t size() const {
|
|
return _size;
|
|
}
|
|
|
|
constexpr size_t capacity() const {
|
|
return _alloc.capacity();
|
|
}
|
|
|
|
constexpr void insert(elem_t&& val) {
|
|
if (_size == 0 or double(_size) / capacity() >= _load) { // expand when full
|
|
_expand();
|
|
}
|
|
|
|
elem_t value = fennec::forward<elem_t>(val);
|
|
size_t i = _hash(value) % capacity(); // Initial search index
|
|
int psl = 0;
|
|
while (_alloc[i].value) { // Search for empty cell
|
|
if (_equal(*_alloc[i].value, val)) { // Check to see if this element is already inserted
|
|
return;
|
|
}
|
|
if (psl > _alloc[i].psl) { // When psl is higher, swap
|
|
fennec::swap(*_alloc[i].value, value);
|
|
fennec::swap(_alloc[i].psl, psl);
|
|
}
|
|
i = (i + 1) % capacity(); ++psl;
|
|
}
|
|
_alloc[i].value = fennec::move(value);
|
|
_alloc[i].psl = psl;
|
|
++_size;
|
|
}
|
|
|
|
constexpr void insert(const elem_t& val) {
|
|
elem_t value = val; // Copy Constructor invoked here
|
|
this->insert(fennec::move(value)); // Only invokes moves
|
|
}
|
|
|
|
template<typename...ArgsT>
|
|
constexpr void emplace(ArgsT&&...args) {
|
|
elem_t value = elem_t(fennec::forward<ArgsT>(args)...); // Constructor invoked here
|
|
this->insert(fennec::move(value)); // Only invokes moves
|
|
}
|
|
|
|
constexpr iterator find(const elem_t& val) const {
|
|
size_t i = _hash(val) % capacity(); // Initial search index
|
|
int psl = 0;
|
|
|
|
// Loop while there is a value and its psl is greater than our probe
|
|
while (_alloc[i].value && _alloc[i].psl <= psl) {
|
|
if (_equal(*_alloc[i].value, val)) {
|
|
return iterator(this, i);
|
|
}
|
|
i = (i + 1) % capacity(); ++psl;
|
|
}
|
|
|
|
return iterator(this, npos);
|
|
}
|
|
|
|
constexpr elem_t* at(const iterator& it) {
|
|
size_t i = it._i;
|
|
if (i >= capacity()) return nullptr;
|
|
if (not _alloc[i].value) return nullptr;
|
|
return &*_alloc[i].value;
|
|
}
|
|
|
|
constexpr const elem_t* at(const iterator& it) const {
|
|
size_t i = it._i;
|
|
if (i >= capacity()) return nullopt;
|
|
if (not _alloc[i].value) return nullopt;
|
|
return &*_alloc[i].value;
|
|
}
|
|
|
|
constexpr bool contains(const elem_t& val) const {
|
|
return this->find(val) != end();
|
|
}
|
|
|
|
constexpr void erase(iterator it) {
|
|
size_t i = it._i;
|
|
if (i >= capacity()) {
|
|
return;
|
|
} // These are separated due to compilers being inconsistent
|
|
if (not _alloc[i].value) {
|
|
return;
|
|
}
|
|
|
|
_alloc[i].value = nullopt;
|
|
--_size;
|
|
size_t p = i;
|
|
while (_alloc[i = (i + 1) % capacity()].value) {
|
|
size_t psl = _alloc[i].psl;
|
|
if (psl == 0) break;
|
|
|
|
fennec::swap(_alloc[i - 1].value, _alloc[i].value);
|
|
_alloc[p].psl = psl - 1;
|
|
p = i;
|
|
}
|
|
}
|
|
|
|
constexpr void erase(const elem_t& val) {
|
|
this->erase(this->find(val));
|
|
}
|
|
|
|
|
|
// ITERATOR ============================================================================================================
|
|
|
|
class iterator {
|
|
public:
|
|
constexpr ~iterator() {
|
|
_set = nullptr;
|
|
}
|
|
|
|
// prefix operator
|
|
constexpr friend iterator& operator++(iterator& rhs) {
|
|
while (++rhs._i < rhs._set->capacity()) {
|
|
if (rhs._set->_alloc[rhs._i].value) {
|
|
return rhs;
|
|
}
|
|
}
|
|
rhs._i = npos;
|
|
return rhs;
|
|
}
|
|
|
|
constexpr friend iterator operator++(iterator& lhs, int) {
|
|
iterator prev = lhs;
|
|
++lhs;
|
|
return prev;
|
|
}
|
|
|
|
constexpr const elem_t& operator*() const {
|
|
return *_set->_alloc[_i].value;
|
|
}
|
|
|
|
constexpr bool operator==(const iterator& it) {
|
|
return _set == it._set and _i == it._i;
|
|
}
|
|
|
|
constexpr bool operator!=(const iterator& it) {
|
|
return _set != it._set or _i != it._i;
|
|
}
|
|
|
|
private:
|
|
const set* _set;
|
|
size_t _i;
|
|
friend set;
|
|
|
|
constexpr iterator(const set* set, size_t i)
|
|
: _set(set)
|
|
, _i(i) {
|
|
}
|
|
};
|
|
|
|
constexpr iterator begin() const {
|
|
iterator it(this, 0);
|
|
if (not _alloc[it._i].value) {
|
|
++it;
|
|
}
|
|
return it;
|
|
}
|
|
|
|
constexpr iterator end() const {
|
|
return iterator(this, npos);
|
|
}
|
|
|
|
|
|
// PRIVATE =============================================================================================================
|
|
|
|
private:
|
|
constexpr void _expand() {
|
|
set cpy; // Create a new set
|
|
cpy._alloc.callocate(
|
|
fennec::next_prime2(_alloc.capacity())
|
|
);
|
|
|
|
// rehash
|
|
for (size_t i = 0; i < capacity(); ++i) {
|
|
if (_alloc[i].value) {
|
|
cpy.insert(fennec::move(*_alloc[i].value));
|
|
}
|
|
}
|
|
|
|
// Swap buffers
|
|
fennec::swap(_alloc, cpy._alloc);
|
|
}
|
|
|
|
allocation<node, alloc_t> _alloc;
|
|
hash_t _hash;
|
|
equal_t _equal;
|
|
size_t _size;
|
|
double _load;
|
|
};
|
|
|
|
}
|
|
|
|
#endif // FENNEC_CONTAINERS_SET_H
|