initial commit
All checks were successful
Build (Arch Linux) / build (push) Successful in 3m10s

This commit is contained in:
2025-04-16 01:58:29 +01:00
commit a8d8b9b9ab
116 changed files with 106633 additions and 0 deletions

View File

@@ -0,0 +1,152 @@
#pragma once
#include "containers/sparse_set.hpp"
#include <cstddef> // std::size_t
#include <format> // std::format
#include <stdexcept> // std::out_of_range
#include <utility> // std::get
#include <vector> // values container
namespace kuiper
{
namespace containers
{
template<typename K, typename V>
class sparse_map {
public:
using sparse_set_t = containers::sparse_set<K>;
using values_container_t = std::vector<V>;
public:
// Capacity
/// Reserve a number of slots in the underlying containers
void reserve(std::size_t size) {
if (size > m_capacity) {
m_values.resize(size);
m_capacity = size;
}
return;
}
// Access
/// Access value specified by key (non-const)
V& at(const K& key) {
if (!m_sparse_set.contains(key)) {
throw std::out_of_range(std::format("key \"{}\" does not exist in the sparse map", key));
}
const auto dense_idx = m_sparse_set.sparse()[key];
return m_values[dense_idx];
}
/// Access value specified by key (const, read-only)
const V& at(const K& key) const {
if (!m_sparse_set.contains(key)) {
throw std::out_of_range(std::format("key \"{}\" does not exist in the sparse map", key));
}
const auto dense_idx = m_sparse_set.sparse()[key];
return m_values[dense_idx];
}
// Modifiers
void insert(const K& key, const V& value) {
// 1. Insert key into the underlying sparse set
// 2. Insert value into the corresponding index in the sparse map
if (!m_sparse_set.contains(key)) {
// Key does not yet exist in set
const auto insertion = m_sparse_set.insert(key);
if (!std::get<1>(insertion))
return;
const auto dense_idx = std::get<0>(insertion);
reserve(dense_idx + 1); // no-op if there's already enough space
m_values[dense_idx] = value;
++m_size;
return;
} else {
// Key already exists in set
const auto dense_idx = m_sparse_set.sparse()[key];
m_values[dense_idx] = value;
++m_size;
return;
}
}
/// Remove an element from the map
void erase(const K& key) {
// 1. Retrieve dense array index from set
// 2. Swap to back
// 3. Erase from set
// 4. Decrement number of elements
if (contains(key)) {
const auto dense_idx = m_sparse_set.sparse()[key];
m_values[dense_idx] = m_values[m_size - 1];
m_sparse_set.erase(key);
--m_size;
}
return;
}
// Lookup
/// Check if the key exists in the map
bool contains(const K& key) const {
return m_sparse_set.contains(key);
}
// Observers
/// Access to the underlying sparse_set container
const sparse_set_t& sparse_set() const noexcept {
return m_sparse_set;
}
/// Access to the underlying values container
const values_container_t& values() const noexcept {
return m_values;
}
// Operator overloads
/// Access/insert element specified by key
V& operator[](const K& key) {
const auto k = key;
if (!contains(k)) {
insert(k, {});
}
return at(k);
}
/// Access element specified by key (const, read-only)
const V& operator[](const K& key) const {
return at(key);
}
private:
containers::sparse_set<K> m_sparse_set {};
values_container_t m_values {};
std::size_t m_size = 0;
std::size_t m_capacity = 0;
};
} // namespace containers
} // namespace kuiper

View File

@@ -0,0 +1,109 @@
#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