Adam Macdonald a8d8b9b9ab
All checks were successful
Build (Arch Linux) / build (push) Successful in 3m10s
initial commit
2025-04-16 01:58:29 +01:00

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