#pragma once #include // std::unsigned_integral #include // std::size_t #include // std::distance #include // std::pair #include // Underlying arrays namespace kuiper { namespace containers { template requires std::unsigned_integral class sparse_set { public: using sparse_container_t = std::vector; using dense_container_t = std::vector; 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 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