All checks were successful
Build (Arch Linux) / build (push) Successful in 3m10s
110 lines
3.3 KiB
C++
110 lines
3.3 KiB
C++
#pragma once
|
|
|
|
#include <concepts> // std::unsigned_integral
|
|
#include <cstddef> // std::size_t
|
|
#include <iterator> // std::distance
|
|
#include <utility> // std::pair
|
|
#include <vector> // Underlying arrays
|
|
|
|
namespace kuiper
|
|
{
|
|
|
|
namespace containers
|
|
{
|
|
|
|
template<typename T>
|
|
requires std::unsigned_integral<T>
|
|
class sparse_set {
|
|
public:
|
|
using sparse_container_t = std::vector<T>;
|
|
using dense_container_t = std::vector<T>;
|
|
|
|
public:
|
|
// Capacity
|
|
|
|
/// Reserve a number of slots in the underlying containers
|
|
void reserve(std::size_t size) {
|
|
if (size > m_capacity) {
|
|
m_sparse.resize(size);
|
|
m_dense.resize(size);
|
|
m_capacity = size;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// Modifiers
|
|
|
|
/// Add an element to the set, returns `(dense index, inserted)` pair
|
|
std::pair<std::size_t, bool> insert(const T& value) {
|
|
// 1. Append to next slot in the dense array
|
|
// 2. Record dense array index (current m_size) in sparse array
|
|
// 3. Increment number of elements
|
|
|
|
if (!contains(value)) {
|
|
reserve(value + 1); // no-op if there's already enough space
|
|
|
|
m_dense[m_size] = value;
|
|
m_sparse[value] = m_size;
|
|
++m_size;
|
|
|
|
return std::make_pair(std::distance(m_dense.cbegin(), m_dense.cbegin() + m_sparse[value]), true);
|
|
} else {
|
|
// Value already exists in set, return the index, but not inserted flag
|
|
return std::make_pair(std::distance(m_dense.cbegin(), m_dense.cbegin() + m_sparse[value]), false);
|
|
}
|
|
}
|
|
|
|
/// Remove an element from the set
|
|
void erase(const T& value) {
|
|
// 1. Swap element to be removed to the back of the dense array
|
|
// 2. Update the dense array index for the value which was swapped
|
|
// 3. Decrement number of elements
|
|
|
|
if (contains(value)) {
|
|
m_dense[m_sparse[value]] = m_dense[m_size - 1];
|
|
// m_dense[m_size - 1] = m_dense[m_sparse[value]];
|
|
m_sparse[m_dense[m_size - 1]] = m_sparse[value];
|
|
--m_size;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/// Clear all elements from the set
|
|
void clear() {
|
|
m_size = 0;
|
|
return;
|
|
}
|
|
|
|
// Lookup
|
|
|
|
/// Check if a value is in the set
|
|
bool contains(const T& value) const {
|
|
// First condition will short-circuit if `value` is out-of-range
|
|
return !(value >= m_capacity) && (m_sparse[value] < m_size) && (m_dense[m_sparse[value]] == value);
|
|
}
|
|
|
|
// Observers
|
|
|
|
/// Access to the underlying sparse container
|
|
const sparse_container_t& sparse() const noexcept {
|
|
return m_sparse;
|
|
}
|
|
|
|
/// Access to the underlying dense container
|
|
const dense_container_t& dense() const noexcept {
|
|
return m_dense;
|
|
}
|
|
|
|
private:
|
|
sparse_container_t m_sparse {};
|
|
dense_container_t m_dense {};
|
|
std::size_t m_size = 0; // Number of elements, also the next index into m_dense
|
|
std::size_t m_capacity = 0; // Number of elements + 1
|
|
};
|
|
|
|
} // namespace containers
|
|
|
|
} // namespace kuiper
|