Helios Engine 0.1.0
A modular ECS based data-oriented C++23 game engine
 
Loading...
Searching...
No Matches
sparse_set.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <helios/core_pch.hpp>
4
7
8#include <algorithm>
9#include <concepts>
10#include <cstddef>
11#include <limits>
12#include <memory>
13#include <span>
14#include <string_view>
15#include <type_traits>
16#include <vector>
17
19
20/**
21 * @brief A sparse set data structure for efficient mapping of sparse indices to dense storage of values.
22 * @details SparseSet provides O(1) insertion, deletion, and lookup operations using two arrays:
23 * - sparse: maps element indices to dense indices
24 * - dense: stores packed values of type T in contiguous memory
25 *
26 * The data structure is particularly useful for managing sparse collections where indices
27 * may have large gaps but you want cache-friendly iteration over existing values.
28 *
29 * Memory complexity: O(max_index + num_elements)
30 * Time complexity: O(1) for all operations (amortized for insertions)
31 *
32 * @tparam T Type of values stored in the dense array
33 * @tparam IndexType Type used for element indices (default: size_t)
34 * @tparam Allocator Allocator type for memory management (default: std::allocator<T>)
35 */
36template <typename T, typename IndexType = size_t, typename Allocator = std::allocator<T>>
37class SparseSet {
38private:
39 using DenseIndexType = IndexType;
40 using SparseAllocator = typename std::allocator_traits<Allocator>::template rebind_alloc<DenseIndexType>;
41 using DenseAllocator = Allocator;
42
43public:
44 using value_type = T;
45 using index_type = IndexType;
46 using dense_index_type = DenseIndexType;
47 using size_type = size_t;
48 using reference = T&;
49 using const_reference = const T&;
50 using pointer = T*;
51 using const_pointer = const T*;
52 using allocator_type = Allocator;
53
54 using sparse_container_type = std::vector<DenseIndexType, SparseAllocator>;
55 using dense_container_type = std::vector<T, DenseAllocator>;
56
57 using iterator = typename dense_container_type::iterator;
58 using const_iterator = typename dense_container_type::const_iterator;
59 using reverse_iterator = typename dense_container_type::reverse_iterator;
60 using const_reverse_iterator = typename dense_container_type::const_reverse_iterator;
61
62 static constexpr IndexType kInvalidIndex = std::numeric_limits<IndexType>::max();
63 static constexpr DenseIndexType kInvalidDenseIndex = std::numeric_limits<DenseIndexType>::max();
64
65 /**
66 * @brief Default constructor.
67 * @details Creates an empty sparse set.
68 * Exception safety: No-throw guarantee if T and Allocator are nothrow default constructible.
69 */
70 constexpr SparseSet() noexcept(std::is_nothrow_default_constructible_v<Allocator>) = default;
71
72 /**
73 * @brief Constructor with custom allocator.
74 * @details Creates an empty sparse set with the specified allocator.
75 * Exception safety: Basic guarantee.
76 * @param alloc The allocator to use for memory management
77 * @throws May throw if allocator copy constructor throws
78 */
79 explicit SparseSet(const Allocator& alloc) noexcept(noexcept(SparseAllocator(alloc)))
80 : sparse_(SparseAllocator(alloc)), dense_(alloc) {}
81
82 /**
83 * @brief Copy constructor.
84 * @details Creates a copy of another sparse set.
85 * Exception safety: Strong guarantee.
86 * @param other The sparse set to copy from
87 * @throws May throw if T copy constructor or memory allocation fails
88 */
89 constexpr SparseSet(const SparseSet& other) = default;
90 /**
91 * @brief Move constructor.
92 * @details Transfers ownership of resources from other sparse set.
93 * Exception safety: No-throw guarantee.
94 */
95 constexpr SparseSet(SparseSet&&) noexcept = default;
96
97 /**
98 * @brief Destructor.
99 * @details Destroys the sparse set and releases all memory.
100 */
101 constexpr ~SparseSet() = default;
102
103 /**
104 * @brief Copy assignment operator.
105 * @details Assigns the contents of another sparse set to this one.
106 * Exception safety: Strong guarantee.
107 * @param other The sparse set to copy from
108 * @return Reference to this sparse set
109 * @throws May throw if T copy assignment or memory allocation fails
110 */
111 constexpr SparseSet& operator=(const SparseSet& other) = default;
112
113 /**
114 * @brief Move assignment operator.
115 * @details Transfers ownership of resources from other sparse set.
116 * Exception safety: No-throw guarantee.
117 * @return Reference to this sparse set
118 */
119 constexpr SparseSet& operator=(SparseSet&&) noexcept = default;
120
121 /**
122 * @brief Clears the set, removing all elements.
123 * @details Removes all values from the set.
124 * The sparse array is reset to invalid indices but its capacity is preserved for performance.
125 * Time complexity: O(sparse_capacity).
126 * Exception safety: No-throw guarantee.
127 */
128 constexpr void Clear() noexcept;
129
130 /**
131 * @brief Inserts a value at the specified index (copy version).
132 * @details Adds the specified value to the set at the given index if it's not already present.
133 * If the index already exists, the existing value is replaced.
134 * The sparse array will be resized if necessary to accommodate the index.
135 * Time complexity: O(1) amortized (O(index) worst case if sparse array needs resizing).
136 * Exception safety: Strong guarantee.
137 * @warning Triggers assertion if index is invalid or negative
138 * @param index The index to insert at
139 * @param value The value to move insert
140 * @return The dense index where the value was stored
141 * @throws std::bad_alloc If memory allocation fails during sparse array resize or dense array growth
142 */
143 DenseIndexType Insert(IndexType index, const T& value);
144
145 /**
146 * @brief Inserts a value at the specified index (move version).
147 * @details Adds the specified value to the set at the given index if it's not already present.
148 * If the index already exists, the existing value is replaced.
149 * The sparse array will be resized if necessary to accommodate the index.
150 * Time complexity: O(1) amortized (O(index) worst case if sparse array needs resizing).
151 * Exception safety: Strong guarantee.
152 * @warning Triggers assertion if index is invalid or negative
153 * @param index The index to insert at
154 * @param value The value to copy insert
155 * @return The dense index where the value was stored
156 * @throws std::bad_alloc If memory allocation fails during sparse array resize or dense array growth
157 */
158 DenseIndexType Insert(IndexType index, T&& value);
159
160 /**
161 * @brief Constructs a value in-place at the specified index.
162 * @details Constructs a value directly in the dense array at the given index.
163 * If the index already exists, the existing value is replaced.
164 * The sparse array will be resized if necessary to accommodate the index.
165 * Time complexity: O(1) amortized (O(index) worst case if sparse array needs resizing).
166 * Exception safety: Strong guarantee.
167 * @warning Triggers assertion if index is invalid or negative.
168 * @param index The index to insert at
169 * @param args Arguments to forward to T's constructor
170 * @return The dense index where the value was stored
171 * @throws std::bad_alloc If memory allocation fails during sparse array resize or dense array growth
172 */
173 template <typename... Args>
174 DenseIndexType Emplace(IndexType index, Args&&... args);
175
176 /**
177 * @brief Removes an index from the set.
178 * @details Removes the specified index from the set using swap-and-pop technique to maintain dense packing.
179 * The last element in the dense array is moved to fill the gap left by the removed element.
180 * Time complexity: O(1).
181 * Exception safety: No-throw guarantee.
182 * @warning Triggers assertion if index is invalid, negative, or doesn't exist.
183 * @param index The index to remove
184 */
185 void Remove(IndexType index) noexcept;
186
187 /**
188 * @brief Reserves space for at least n elements in the dense array.
189 * @details Ensures that the dense array can hold at least n elements without triggering a reallocation.
190 * Does not affect the sparse array capacity.
191 * Time complexity: O(1) if no reallocation, O(Size()) if reallocation occurs.
192 * Exception safety: Strong guarantee.
193 * @warning Triggers assertion if n is negative.
194 * @param n The minimum capacity to reserve
195 * @throws std::bad_alloc If memory allocation fails
196 */
197 void Reserve(size_type n) { dense_.reserve(n); }
198
199 /**
200 * @brief Reserves space for indices up to max_index in the sparse array.
201 * @details Ensures that the sparse array can accommodate indices up to max_index without triggering a reallocation.
202 * Time complexity: O(1) if no reallocation, O(max_index) if reallocation occurs.
203 * Exception safety: Strong guarantee.
204 * @warning Triggers assertion if max_index is invalid or negative.
205 * @param max_index The maximum index to accommodate
206 * @throws std::bad_alloc If memory allocation fails
207 */
208 void ReserveSparse(IndexType max_index);
209
210 /**
211 * @brief Shrinks the capacity of both arrays to fit their current size.
212 * @details Reduces memory usage by shrinking both the dense and sparse arrays to their minimum required size.
213 * Time complexity: O(Size() + SparseSize()).
214 * Exception safety: Strong guarantee.
215 * @throws std::bad_alloc If memory allocation fails during shrinking
216 */
218
219 /**
220 * @brief Gets the value at the specified index.
221 * @details Returns a reference to the value stored at the given index.
222 * Time complexity: O(1).
223 * Exception safety: No-throw guarantee.
224 * @warning Triggers assertion if index is invalid, negative, or doesn't exist.
225 * @param index The index to access
226 * @return Reference to the value at the specified index
227 */
228 [[nodiscard]] constexpr T& Get(IndexType index) noexcept;
229
230 /**
231 * @brief Gets the value at the specified index (const version).
232 * @details Returns a const reference to the value stored at the given index.
233 * Time complexity: O(1).
234 * Exception safety: No-throw guarantee.
235 * @warning Triggers assertion if index is invalid, negative, or doesn't exist.
236 * @param index The index to access
237 * @return Const reference to the value at the specified index
238 */
239 [[nodiscard]] constexpr const T& Get(IndexType index) const noexcept;
240
241 /**
242 * @brief Gets the value at the specified dense index.
243 * @details Returns a reference to the value stored at the given dense position.
244 * Time complexity: O(1).
245 * Exception safety: No-throw guarantee.
246 * @warning Triggers assertion if dense_index is invalid, negative, or out of bounds.
247 * @param dense_index The dense position to access
248 * @return Reference to the value at the specified dense position
249 */
250 [[nodiscard]] constexpr T& GetByDenseIndex(DenseIndexType dense_index) noexcept;
251
252 /**
253 * @brief Gets the value at the specified dense index (const version).
254 * @details Returns a const reference to the value stored at the given dense position.
255 * Time complexity: O(1).
256 * Exception safety: No-throw guarantee.
257 * @warning Triggers assertion if dense_index is invalid, negative, or out of bounds.
258 * @param dense_index The dense position to access
259 * @return Const reference to the value at the specified dense position
260 */
261 [[nodiscard]] constexpr const T& GetByDenseIndex(DenseIndexType dense_index) const noexcept;
262
263 /**
264 * @brief Tries to get the value at the specified index.
265 * @details Returns a pointer to the value stored at the given index, or nullptr if the index doesn't exist.
266 * Time complexity: O(1).
267 * Exception safety: No-throw guarantee.
268 * @param index The index to access
269 * @return Pointer to the value at the specified index, or nullptr if index doesn't exist
270 */
271 [[nodiscard]] constexpr T* TryGet(IndexType index) noexcept;
272
273 /**
274 * @brief Tries to get the value at the specified index (const version).
275 * @details Returns a pointer to the value stored at the given index, or nullptr if the index doesn't exist.
276 * Time complexity: O(1).
277 * Exception safety: No-throw guarantee.
278 * @param index The index to access
279 * @return Pointer to the value at the specified index, or nullptr if index doesn't exist
280 */
281 [[nodiscard]] constexpr const T* TryGet(IndexType index) const noexcept;
282
283 /**
284 * @brief Swaps the contents of this sparse set with another.
285 * @details Efficiently swaps all data between two sparse sets.
286 * Time complexity: O(1).
287 * Exception safety: No-throw guarantee.
288 * @param other The sparse set to swap with
289 */
290 void Swap(SparseSet& other) noexcept;
291
292 /**
293 * @brief Checks if two sparse sets are equal.
294 * @details Two sparse sets are equal if they contain the same index-value pairs.
295 * Time complexity: O(Size() + other.Size()).
296 * Exception safety: No-throw guarantee.
297 * @param other The sparse set to compare with
298 * @return True if both sets contain the same index-value pairs
299 */
300 [[nodiscard]] bool operator==(const SparseSet& other) const noexcept
301 requires std::equality_comparable<T>;
302
303 /**
304 * @brief Checks if the set is empty.
305 * @details Returns true if no values are stored in the set.
306 * Time complexity: O(1).
307 * Exception safety: No-throw guarantee.
308 * @return True if the set contains no elements, false otherwise
309 */
310 [[nodiscard]] constexpr bool Empty() const noexcept { return dense_.empty(); }
311
312 /**
313 * @brief Checks if an index value is valid for this sparse set.
314 * @details Returns true if the index is not the reserved invalid value.
315 * Time complexity: O(1).
316 * Exception safety: No-throw guarantee.
317 * @param index The index to validate
318 * @return True if the index is valid, false if it's the reserved invalid value
319 */
320 [[nodiscard]] static constexpr bool IsValidIndex(IndexType index) noexcept { return index != kInvalidIndex; }
321
322 /**
323 * @brief Checks if an index exists in the set.
324 * @details Performs a comprehensive check to ensure the index is valid and exists in the set.
325 * Time complexity: O(1).
326 * Exception safety: No-throw guarantee.
327 * @warning Triggers assertion if index is invalid or negative.
328 * @param index The index to check
329 * @return True if the index exists in the set, false otherwise
330 */
331 [[nodiscard]] constexpr bool Contains(IndexType index) const noexcept;
332
333 /**
334 * @brief Returns the allocator associated with the container.
335 * @details Gets the allocator used for the dense array.
336 * Time complexity: O(1).
337 * Exception safety: No-throw guarantee.
338 * @return Copy of the allocator
339 */
340 [[nodiscard]] constexpr allocator_type GetAllocator() const noexcept { return dense_.get_allocator(); }
341
342 /**
343 * @brief Gets the dense index for a given index.
344 * @details Returns the position of the value in the dense array for the specified index.
345 * Time complexity: O(1).
346 * Exception safety: No-throw guarantee.
347 * @warning Triggers assertion if index is invalid, negative, or doesn't exist.
348 * @param index The index to look up
349 * @return The dense index
350 */
351 [[nodiscard]] constexpr DenseIndexType GetDenseIndex(IndexType index) const noexcept;
352
353 /**
354 * @brief Returns the number of values in the set.
355 * @details Returns the count of values currently stored in the set.
356 * Time complexity: O(1).
357 * Exception safety: No-throw guarantee.
358 * @return The number of values in the set
359 */
360 [[nodiscard]] constexpr size_type Size() const noexcept { return dense_.size(); }
361
362 /**
363 * @brief Returns the maximum possible size of the set.
364 * @details Returns the theoretical maximum number of elements the set can hold.
365 * Time complexity: O(1).
366 * Exception safety: No-throw guarantee.
367 * @return The maximum possible size
368 */
369 [[nodiscard]] constexpr size_type MaxSize() const noexcept { return dense_.max_size(); }
370
371 /**
372 * @brief Returns the capacity of the dense array.
373 * @details Returns the number of elements that can be stored in the dense array without triggering a reallocation.
374 * Time complexity: O(1).
375 * Exception safety: No-throw guarantee.
376 * @return The capacity of the dense array
377 */
378 [[nodiscard]] constexpr size_type Capacity() const noexcept { return dense_.capacity(); }
379
380 /**
381 * @brief Returns the capacity of the sparse array.
382 * @details Returns the maximum index that can be stored without triggering a sparse array reallocation.
383 * Time complexity: O(1).
384 * Exception safety: No-throw guarantee.
385 * @return The capacity of the sparse array
386 */
387 [[nodiscard]] constexpr size_type SparseCapacity() const noexcept { return sparse_.capacity(); }
388
389 /**
390 * @brief Returns a writable span of the packed values.
391 * @details Provides direct access to the densely packed values in the order
392 * they were inserted (with removal gaps filled by later insertions).
393 * Time complexity: O(1).
394 * Exception safety: No-throw guarantee.
395 * @return A span containing all values in the set
396 */
397 [[nodiscard]] constexpr std::span<T> Data() noexcept { return dense_; }
398
399 /**
400 * @brief Returns a read-only span of the packed values.
401 * @details Provides direct access to the densely packed values in the order
402 * they were inserted (with removal gaps filled by later insertions).
403 * Time complexity: O(1).
404 * Exception safety: No-throw guarantee.
405 * @return A span containing all values in the set
406 */
407 [[nodiscard]] constexpr std::span<const T> Data() const noexcept { return dense_; }
408
409 [[nodiscard]] constexpr iterator begin() noexcept { return dense_.begin(); }
410 [[nodiscard]] constexpr const_iterator begin() const noexcept { return dense_.begin(); }
411 [[nodiscard]] constexpr const_iterator cbegin() const noexcept { return dense_.cbegin(); }
412
413 [[nodiscard]] constexpr iterator end() noexcept { return dense_.end(); }
414 [[nodiscard]] constexpr const_iterator end() const noexcept { return dense_.end(); }
415 [[nodiscard]] constexpr const_iterator cend() const noexcept { return dense_.cend(); }
416
417 [[nodiscard]] constexpr reverse_iterator rbegin() noexcept { return dense_.rbegin(); }
418 [[nodiscard]] constexpr const_reverse_iterator rbegin() const noexcept { return dense_.rbegin(); }
419 [[nodiscard]] constexpr const_reverse_iterator crbegin() const noexcept { return dense_.crbegin(); }
420
421 [[nodiscard]] constexpr reverse_iterator rend() noexcept { return dense_.rend(); }
422 [[nodiscard]] constexpr const_reverse_iterator rend() const noexcept { return dense_.rend(); }
423 [[nodiscard]] constexpr const_reverse_iterator crend() const noexcept { return dense_.crend(); }
424
425private:
426 sparse_container_type sparse_; ///< Maps index -> dense index
427 dense_container_type dense_; ///< Packed values in insertion order
428 std::vector<IndexType> reverse_map_; ///< Maps dense index -> original index for efficient removal
429};
430
431template <typename T, typename IndexType, typename Allocator>
433 dense_.clear();
434 reverse_map_.clear();
435 std::ranges::fill(sparse_, kInvalidDenseIndex);
436}
437
438template <typename T, typename IndexType, typename Allocator>
439inline typename SparseSet<T, IndexType, Allocator>::DenseIndexType SparseSet<T, IndexType, Allocator>::Insert(
440 IndexType index, const T& value) {
441 HELIOS_ASSERT(IsValidIndex(index), "Failed to insert value: index is invalid!");
442 if constexpr (std::is_signed_v<IndexType>) {
443 HELIOS_ASSERT(index >= 0, "Failed to insert value: index cannot be negative!");
444 }
445
446 // Check if index already exists and replace the value
447 if (Contains(index)) {
448 const DenseIndexType dense_index = sparse_[index];
449 dense_[dense_index] = value;
450 return dense_index;
451 }
452
453 // Ensure sparse array can accommodate this index
454 if (index >= sparse_.size()) {
455 sparse_.resize(index + 1, kInvalidDenseIndex);
456 }
457
458 const auto dense_index = static_cast<DenseIndexType>(dense_.size());
459 dense_.push_back(value);
460 reverse_map_.push_back(index);
461 sparse_[index] = dense_index;
462
463 return dense_index;
464}
465
466template <typename T, typename IndexType, typename Allocator>
467inline typename SparseSet<T, IndexType, Allocator>::DenseIndexType SparseSet<T, IndexType, Allocator>::Insert(
468 IndexType index, T&& value) {
469 HELIOS_ASSERT(IsValidIndex(index), "Failed to insert value: index is invalid!");
470 if constexpr (std::is_signed_v<IndexType>) {
471 HELIOS_ASSERT(index >= 0, "Failed to insert value: index cannot be negative!");
472 }
473
474 // Check if index already exists and replace the value
475 if (Contains(index)) {
476 const DenseIndexType dense_index = sparse_[index];
477 dense_[dense_index] = std::move(value);
478 return dense_index;
479 }
480
481 // Ensure sparse array is large enough
482 if (static_cast<size_type>(index) >= sparse_.size()) {
483 sparse_.resize(static_cast<size_type>(index) + 1, kInvalidDenseIndex);
484 }
485
486 const auto dense_index = static_cast<DenseIndexType>(dense_.size());
487 sparse_[index] = dense_index;
488 dense_.push_back(std::move(value));
489 reverse_map_.push_back(index);
490
491 return dense_index;
492}
493
494template <typename T, typename IndexType, typename Allocator>
495template <typename... Args>
496inline typename SparseSet<T, IndexType, Allocator>::DenseIndexType SparseSet<T, IndexType, Allocator>::Emplace(
497 IndexType index, Args&&... args) {
498 HELIOS_ASSERT(IsValidIndex(index), "Failed to emplace value: index is invalid!");
499 if constexpr (std::is_signed_v<IndexType>) {
500 HELIOS_ASSERT(index >= 0, "Failed to emplace value: index cannot be negative!");
501 }
502
503 // Check if index already exists and replace the value
504 if (Contains(index)) {
505 const DenseIndexType dense_index = sparse_[index];
506 dense_[dense_index] = T(std::forward<Args>(args)...);
507 return dense_index;
508 }
509
510 // Ensure sparse array is large enough
511 if (static_cast<size_type>(index) >= sparse_.size()) {
512 sparse_.resize(static_cast<size_type>(index) + 1, kInvalidDenseIndex);
513 }
514
515 const auto dense_index = static_cast<DenseIndexType>(dense_.size());
516 sparse_[index] = dense_index;
517 dense_.emplace_back(std::forward<Args>(args)...);
518 reverse_map_.push_back(index);
519
520 return dense_index;
521}
522
523template <typename T, typename IndexType, typename Allocator>
524inline void SparseSet<T, IndexType, Allocator>::Remove(IndexType index) noexcept {
525 HELIOS_ASSERT(IsValidIndex(index), "Failed to remove value: index is invalid!");
526 if constexpr (std::is_signed_v<IndexType>) {
527 HELIOS_ASSERT(index >= 0, "Failed to remove value: index cannot be negative!");
528 }
529 HELIOS_ASSERT(Contains(index), "Failed to remove value: index does not exist!");
530
531 const DenseIndexType dense_index = sparse_[index];
532 const auto last_dense_index = static_cast<DenseIndexType>(dense_.size() - 1);
533
534 if (dense_index != last_dense_index) {
535 // Swap with last element to maintain dense packing
536 const IndexType last_index = reverse_map_[last_dense_index];
537 dense_[dense_index] = std::move(dense_[last_dense_index]);
538 reverse_map_[dense_index] = last_index;
539 sparse_[last_index] = dense_index;
540 }
541
542 dense_.pop_back();
543 reverse_map_.pop_back();
544 sparse_[index] = kInvalidDenseIndex;
545}
546
547template <typename T, typename IndexType, typename Allocator>
549 HELIOS_ASSERT(IsValidIndex(max_index), "Failed to reserve sparse: max_index is invalid!");
550 if constexpr (std::is_signed_v<IndexType>) {
551 HELIOS_ASSERT(max_index >= 0, "Failed to reserve sparse: max_index cannot be negative!");
552 }
553 if (static_cast<size_type>(max_index) + 1 > sparse_.size()) {
554 sparse_.resize(static_cast<size_type>(max_index) + 1, kInvalidDenseIndex);
555 }
556}
557
558template <typename T, typename IndexType, typename Allocator>
560 dense_.shrink_to_fit();
561 reverse_map_.shrink_to_fit();
562
563 // Find the maximum index in use to determine minimum sparse size needed
564 if (reverse_map_.empty()) {
565 sparse_.clear();
566 } else {
567 const IndexType max_index = *std::ranges::max_element(reverse_map_);
568 sparse_.resize(static_cast<size_type>(max_index) + 1);
569 }
570 sparse_.shrink_to_fit();
571}
572
573template <typename T, typename IndexType, typename Allocator>
574constexpr T& SparseSet<T, IndexType, Allocator>::Get(IndexType index) noexcept {
575 HELIOS_ASSERT(IsValidIndex(index), "Failed to get value: index is invalid!");
576 if constexpr (std::is_signed_v<IndexType>) {
577 HELIOS_ASSERT(index >= 0, "Failed to get value: index cannot be negative!");
578 }
579 HELIOS_ASSERT(Contains(index), "Failed to get value: index does not exist!");
580 return dense_[sparse_[index]];
581}
582
583template <typename T, typename IndexType, typename Allocator>
584constexpr const T& SparseSet<T, IndexType, Allocator>::Get(IndexType index) const noexcept {
585 HELIOS_ASSERT(IsValidIndex(index), "Failed to get value: index is invalid!");
586 if constexpr (std::is_signed_v<IndexType>) {
587 HELIOS_ASSERT(index >= 0, "Failed to get value: index cannot be negative!");
588 }
589 HELIOS_ASSERT(Contains(index), "Failed to get value: index does not exist!");
590 return dense_[sparse_[index]];
591}
592
593template <typename T, typename IndexType, typename Allocator>
594constexpr T& SparseSet<T, IndexType, Allocator>::GetByDenseIndex(DenseIndexType dense_index) noexcept {
595 HELIOS_ASSERT(dense_index != kInvalidDenseIndex, "Failed to get value: dense_index is invalid!");
596 if constexpr (std::is_signed_v<DenseIndexType>) {
597 HELIOS_ASSERT(dense_index >= 0, "Failed to get value: dense_index cannot be negative!");
598 }
599 HELIOS_ASSERT(dense_index < dense_.size(), "Failed to get value: dense_index is out of bounds!");
600 return dense_[dense_index];
601}
602
603template <typename T, typename IndexType, typename Allocator>
604constexpr const T& SparseSet<T, IndexType, Allocator>::GetByDenseIndex(DenseIndexType dense_index) const noexcept {
605 HELIOS_ASSERT(dense_index != kInvalidDenseIndex, "Failed to get value: dense_index is invalid!");
606 if constexpr (std::is_signed_v<DenseIndexType>) {
607 HELIOS_ASSERT(dense_index >= 0, "Failed to get value: dense_index cannot be negative!");
608 }
609 HELIOS_ASSERT(dense_index < dense_.size(), "Failed to get value: dense_index is out of bounds!");
610 return dense_[dense_index];
611}
612
613template <typename T, typename IndexType, typename Allocator>
614constexpr T* SparseSet<T, IndexType, Allocator>::TryGet(IndexType index) noexcept {
615 HELIOS_ASSERT(index != kInvalidIndex, "Failed to try get value: index is invalid!");
616 if constexpr (std::is_signed_v<IndexType>) {
617 HELIOS_ASSERT(index >= 0, "Failed to try get value: index cannot be negative!");
618 }
619
620 if (!Contains(index)) {
621 return nullptr;
622 }
623 return &dense_[sparse_[index]];
624}
625
626template <typename T, typename IndexType, typename Allocator>
627constexpr const T* SparseSet<T, IndexType, Allocator>::TryGet(IndexType index) const noexcept {
628 HELIOS_ASSERT(index != kInvalidIndex, "Failed to get value: index is invalid!");
629 if constexpr (std::is_signed_v<IndexType>) {
630 HELIOS_ASSERT(index >= 0, "Failed to try get value: index cannot be negative!");
631 }
632
633 if (!Contains(index)) {
634 return nullptr;
635 }
636 return &dense_[sparse_[index]];
637}
638
639template <typename T, typename IndexType, typename Allocator>
641 std::swap(sparse_, other.sparse_);
642 std::swap(dense_, other.dense_);
643 std::swap(reverse_map_, other.reverse_map_);
644}
645
646template <typename T, typename IndexType, typename Allocator>
647inline bool SparseSet<T, IndexType, Allocator>::operator==(const SparseSet& other) const noexcept
648 requires std::equality_comparable<T>
649{
650 if (Size() != other.Size()) {
651 return false;
652 }
653
654 for (size_type i = 0; i < dense_.size(); ++i) {
655 const IndexType index = reverse_map_[i];
656 if (!other.Contains(index) || dense_[i] != other.Get(index)) {
657 return false;
658 }
659 }
660
661 return true;
662}
663
664template <typename T, typename IndexType, typename Allocator>
665constexpr bool SparseSet<T, IndexType, Allocator>::Contains(IndexType index) const noexcept {
666 HELIOS_ASSERT(IsValidIndex(index), "Failed to check if set contains index: index is invalid!");
667 if constexpr (std::is_signed_v<IndexType>) {
668 HELIOS_ASSERT(index >= 0, "Failed to check if set contains index: index cannot be negative!");
669 }
670 return static_cast<size_type>(index) < sparse_.size() && sparse_[index] != kInvalidDenseIndex &&
671 static_cast<size_type>(sparse_[index]) < dense_.size() &&
672 static_cast<size_type>(sparse_[index]) < reverse_map_.size() && reverse_map_[sparse_[index]] == index;
673}
674
675template <typename T, typename IndexType, typename Allocator>
676constexpr typename SparseSet<T, IndexType, Allocator>::DenseIndexType SparseSet<T, IndexType, Allocator>::GetDenseIndex(
677 IndexType index) const noexcept {
678 HELIOS_ASSERT(IsValidIndex(index), "Failed to get dense index: index is invalid!");
679 if constexpr (std::is_signed_v<IndexType>) {
680 HELIOS_ASSERT(index >= 0, "Failed to get dense index: index cannot be negative!");
681 }
682 HELIOS_ASSERT(Contains(index), "Failed to get dense index: index does not exist!");
683 return sparse_[index];
684}
685
686/**
687 * @brief Swaps two sparse sets.
688 * @details Non-member swap function for efficient swapping of sparse sets.
689 * Time complexity: O(1).
690 * Exception safety: No-throw guarantee.
691 * @tparam T Type of values stored
692 * @tparam IndexType Type used for indices
693 * @tparam Allocator Allocator type
694 * @param lhs First sparse set
695 * @param rhs Second sparse set
696 */
697template <typename T, typename IndexType, typename Allocator>
699 lhs.Swap(rhs);
700}
701
702} // namespace helios::container
#define HELIOS_ASSERT(condition,...)
Assertion macro that aborts execution in debug builds.
Definition assert.hpp:140
A sparse set data structure for efficient mapping of sparse indices to dense storage of values.
constexpr T & GetByDenseIndex(DenseIndexType dense_index) noexcept
Gets the value at the specified dense index.
constexpr void Clear() noexcept
Clears the set, removing all elements.
constexpr const T & Get(IndexType index) const noexcept
Gets the value at the specified index (const version).
constexpr const_iterator cbegin() const noexcept
typename dense_container_type::const_reverse_iterator const_reverse_iterator
void ReserveSparse(IndexType max_index)
Reserves space for indices up to max_index in the sparse array.
constexpr const T & GetByDenseIndex(DenseIndexType dense_index) const noexcept
Gets the value at the specified dense index (const version).
constexpr SparseSet(SparseSet &&) noexcept=default
Move constructor.
void ShrinkToFit()
Shrinks the capacity of both arrays to fit their current size.
constexpr SparseSet(const SparseSet &other)=default
Copy constructor.
constexpr bool Contains(IndexType index) const noexcept
Checks if an index exists in the set.
constexpr const_iterator begin() const noexcept
constexpr iterator end() noexcept
void Swap(SparseSet &other) noexcept
Swaps the contents of this sparse set with another.
void Reserve(size_type n)
Reserves space for at least n elements in the dense array.
constexpr const T * TryGet(IndexType index) const noexcept
Tries to get the value at the specified index (const version).
constexpr SparseSet() noexcept(std::is_nothrow_default_constructible_v< Allocator >)=default
Default constructor.
constexpr const_reverse_iterator crend() const noexcept
constexpr size_type MaxSize() const noexcept
Returns the maximum possible size of the set.
constexpr const_iterator cend() const noexcept
constexpr std::span< const T > Data() const noexcept
Returns a read-only span of the packed values.
constexpr const_reverse_iterator rbegin() const noexcept
DenseIndexType dense_index_type
constexpr DenseIndexType GetDenseIndex(IndexType index) const noexcept
Gets the dense index for a given index.
constexpr reverse_iterator rend() noexcept
static constexpr bool IsValidIndex(IndexType index) noexcept
Checks if an index value is valid for this sparse set.
constexpr T * TryGet(IndexType index) noexcept
Tries to get the value at the specified index.
constexpr allocator_type GetAllocator() const noexcept
Returns the allocator associated with the container.
constexpr T & Get(IndexType index) noexcept
Gets the value at the specified index.
std::vector< DenseIndexType, SparseAllocator > sparse_container_type
constexpr bool Empty() const noexcept
Checks if the set is empty.
typename dense_container_type::iterator iterator
constexpr const_iterator end() const noexcept
constexpr const_reverse_iterator rend() const noexcept
constexpr const_reverse_iterator crbegin() const noexcept
constexpr size_type Capacity() const noexcept
Returns the capacity of the dense array.
DenseIndexType Insert(IndexType index, const T &value)
Inserts a value at the specified index (copy version).
constexpr reverse_iterator rbegin() noexcept
DenseIndexType Emplace(IndexType index, Args &&... args)
Constructs a value in-place at the specified index.
constexpr size_type SparseCapacity() const noexcept
Returns the capacity of the sparse array.
typename dense_container_type::const_iterator const_iterator
typename dense_container_type::reverse_iterator reverse_iterator
std::vector< T, DenseAllocator > dense_container_type
void Remove(IndexType index) noexcept
Removes an index from the set.
bool operator==(const SparseSet &other) const noexcept
Checks if two sparse sets are equal.
constexpr size_type Size() const noexcept
Returns the number of values in the set.
static constexpr IndexType kInvalidIndex
static constexpr DenseIndexType kInvalidDenseIndex
constexpr iterator begin() noexcept
constexpr std::span< T > Data() noexcept
Returns a writable span of the packed values.
void swap(SparseSet< T, IndexType, Allocator > &lhs, SparseSet< T, IndexType, Allocator > &rhs) noexcept
Swaps two sparse sets.
STL namespace.