This commit is contained in:
152
include/containers/sparse_map.hpp
Normal file
152
include/containers/sparse_map.hpp
Normal 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
|
||||
109
include/containers/sparse_set.hpp
Normal file
109
include/containers/sparse_set.hpp
Normal 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
|
||||
Reference in New Issue
Block a user