BoundedPriorityDeque
A lightweight, performant bounded priority deque with wide
Loading...
Searching...
No Matches
BoundedPriorityDeque.hpp
Go to the documentation of this file.
1//
2// Created by Cooper Larson on 7/30/24.
3//
4
5#ifndef BOUNDED_PRIORITY_DEQUE_H
6#define BOUNDED_PRIORITY_DEQUE_H
7
8#include <vector>
9
10#ifdef ENABLE_DEBUG
11#include <stdexcept>
12#endif
13
33template <typename T>
35 template <typename U, typename = decltype(std::declval<U>()(std::declval<const T&>(), std::declval<const T&>()))>
36 static std::true_type check(U*);
37
38 static std::false_type check(...);
39
40public:
41 static constexpr bool value = decltype(check(std::declval<T*>()))::value;
42};
43
44template <typename T>
45inline constexpr bool has_comparison_operator_v = has_comparison_operator<T>::value;
46
55template<typename K, typename V, typename Enable = void>
57public:
58 K key;
60
61 bool operator<(const BoundingPair& other) const {
62 return this->key < other.key;
63 }
64};
65
75template<typename K, typename V>
76class BoundingPair<K, V, std::enable_if_t<has_comparison_operator_v<K>>> : public std::pair<K, V> {
77public:
78 bool operator<(const BoundingPair& other) const {
79 K comparator;
80 return comparator(this->key, other.key);
81 }
82};
83
94template<typename K, typename V>
96protected:
97 std::vector<BoundingPair<K, V>> _buffer;
98 size_t _k, _size = 0, _head = 0, _tail = 0;
99
107 virtual bool compare(K a, K b) const = 0;
108
115 [[nodiscard]] size_t nextIndex(size_t current) const { return (current + 1) % _k; }
116
123 [[nodiscard]] size_t prevIndex(size_t current) const { return (current + _k - 1) % _k; }
124
134 size_t binarySearch(const BoundingPair<K, V>& target) const {
135 size_t start = 0;
136 auto end = _size;
137 while (start != end) {
138 size_t mid = start + (end - start) / 2;
139 if (compare(_buffer[(_head + mid) % _k].key, target.key)) start = mid + 1;
140 else end = mid;
141 }
142 return (_head + start) % _k;
143 }
144
156 void insert(const BoundingPair<K, V>& element) {
157 if (_size == 0) {
158 _buffer[0] = element;
159 _head = 0;
160 _tail = 0;
161 _size = 1;
162 return;
163 }
164
165 auto index = binarySearch(element);
166 if (index == nextIndex(_tail)) {
167 if (index == prevIndex(_head) && compare(element.key, topK())) _head = prevIndex(_head);
168 else _tail = nextIndex(_tail);
169 }
170 else if (index == prevIndex(_head)) _head = prevIndex(_head);
171 else if (_head <= _tail && _head > 0) {
172 std::move(_buffer.begin() + _head, _buffer.begin() + index + 1, _buffer.begin() + _head - 1);
173 --_head;
174 } else {
175 std::move_backward(_buffer.begin() + index, _buffer.begin() + _tail + 1, _buffer.begin() + _tail + 2);
176 ++_tail;
177 }
178
179 _buffer[index] = element;
180 ++_size;
181 }
182
186 void _popTop() {
187 _head = nextIndex(_head);
188 if (--_size == 0) clear();
189 }
190
194 void _popBottom() {
195 _tail = prevIndex(_tail);
196 if (--_size == 0) clear();
197 }
198
199public:
205 explicit BoundedPriorityDequeBase(size_t capacity = 0) : _buffer(capacity), _k(capacity) {}
206
213#ifdef ENABLE_DEBUG
214 if (empty()) throw std::runtime_error("Attempted to access top element of empty BoundedPriorityDeque");
215#endif
216 return _buffer[_head];
217 }
218
227#ifdef ENABLE_DEBUG
228 if (empty()) throw std::runtime_error("Attempted to access bottom element of empty BoundedPriorityDeque");
229#endif
230 return _buffer[_tail];
231 }
232
241 [[nodiscard]] K topK() const {
242#ifdef ENABLE_DEBUG
243 if (empty()) throw std::runtime_error("Attempted to access bottom element of empty BoundedPriorityDeque");
244#endif
245 return _buffer[_head].key;
246 }
247
255 [[nodiscard]] K bottomK() const {
256#ifdef ENABLE_DEBUG
257 if (empty()) throw std::runtime_error("Attempted to access bottom element of empty BoundedPriorityDeque");
258#endif
259 return _buffer[_tail].key;
260 }
261
268 void emplace(K key, const V& value) {
269 if (_size == _k) {
270 if (compare(key, _buffer[_tail].key)) _popBottom();
271 else return;
272 }
273 insert({ key, value });
274 }
275
285 void push(const BoundingPair<K, V>& element) {
286 if (_size == _k) {
287 if (compare(element.key, _buffer[_tail].key)) _popBottom();
288 else return;
289 }
290 insert(element);
291 }
292
303 BoundingPair<K, V> operator[](size_t offsetTop) const {
304 return _buffer[_head <= _tail ? _head + offsetTop : (_head + offsetTop) % _k];
305 }
306
313#ifdef ENABLE_DEBUG
314 if (empty()) throw std::runtime_error("Attempted to pop from empty BoundedPriorityDeque");
315#endif
316 auto index = _head;
317 _popTop();
318 return _buffer[index];
319 }
320
327#ifdef ENABLE_DEBUG
328 if (empty()) throw std::runtime_error("Attempted to pop from empty BoundedPriorityDeque");
329#endif
330 auto index = _tail;
331 _popBottom();
332 return _buffer[index];
333 }
334
345 for (size_t i = 0; i < rhs.size(); ++i) {
346 if (_size == _k) {
347 if (compare(rhs[i].key, bottomK())) _popBottom();
348 else return;
349 }
350 insert(rhs[i]);
351 }
352 }
353
359 void clear() {
360 _head = 0;
361 _tail = 0;
362 _size = 0;
363 }
364
369 [[nodiscard]] size_t size() const { return _size; }
370
375 [[nodiscard]] size_t capacity() const { return _k; }
376
381 [[nodiscard]] bool empty() const { return _size == 0; }
382
387 [[nodiscard]] bool full() const { return _size == _k; }
388
398 void resize(size_t k) {
399 if (k == 0) return;
400
401 std::vector<BoundingPair<K, V>> newBuffer(k);
402
403 if (_head <= _tail) {
404 size_t elementsToCopy = std::min(_size, k);
405 std::move(_buffer.begin() + _head, _buffer.begin() + _head + elementsToCopy, newBuffer.begin());
406 _size = elementsToCopy;
407 } else {
408 size_t topSize = _k - _head;
409 size_t bottomSize = _tail + 1;
410 size_t elementsToCopyTop = std::min(topSize, k);
411 size_t elementsToCopyBottom = std::min(bottomSize, k - elementsToCopyTop);
412
413 std::move(_buffer.begin() + _head, _buffer.begin() + _head + elementsToCopyTop, newBuffer.begin());
414 std::move(_buffer.begin(), _buffer.begin() + elementsToCopyBottom, newBuffer.begin() + elementsToCopyTop);
415
416 _size = elementsToCopyTop + elementsToCopyBottom;
417 }
418
419 _buffer.swap(newBuffer);
420 _k = k;
421 _head = 0;
422 _tail = _size - 1;
423 }
424};
425
435template<typename K, typename V>
437protected:
438 [[nodiscard]] bool compare(K a, K b) const override { return a < b; }
439
440public:
441 explicit BoundedMinPriorityDeque(unsigned int capacity = 0) : BoundedPriorityDequeBase<K, V>(capacity) {}
442};
443
453template<typename K, typename V>
455protected:
456 [[nodiscard]] bool compare(K a, K b) const override { return a > b; }
457
458public:
459 explicit BoundedMaxPriorityDeque(unsigned int capacity = 0) : BoundedPriorityDequeBase<K, V>(capacity) {}
460};
461
474template<typename K, typename V, typename Comparator = std::less<K>>
476protected:
477 Comparator comparator;
478
479 [[nodiscard]] bool compare(K a, K b) const override { return comparator(a, b); }
480
481public:
482 explicit BoundedPriorityDequeKeyed(unsigned int capacity = 0, Comparator comp = Comparator()) :
483 BoundedPriorityDequeBase<K, V>(capacity), comparator(comp) {}
484};
485
502template<typename V, typename Comparator, typename K = decltype(std::declval<Comparator>().comparisonValue(std::declval<V>()))>
504protected:
505 Comparator comparator;
506
507 [[nodiscard]] bool compare(K a, K b) const override {
508 return comparator(a, b);
509 }
510
511 K extractKey(const V& value) const {
512 return comparator.comparisonValue(value);
513 }
514
515public:
516 explicit BoundedPriorityDeque(unsigned int capacity = 0, Comparator comp = Comparator()) :
517 BoundedPriorityDequeBase<K, V>(capacity), comparator(comp) {}
518
519 void emplace(const V& value) {
520 K key = extractKey(value);
522 }
523
524 void push(const V& value) { emplace(value); }
525};
526
527#endif // BOUNDED_PRIORITY_DEQUE_H
Lightweight maximum priority dequeue.
Definition BoundedPriorityDeque.hpp:454
bool compare(K a, K b) const override
Definition BoundedPriorityDeque.hpp:456
Lightweight minimum priority dequeue.
Definition BoundedPriorityDeque.hpp:436
bool compare(K a, K b) const override
Definition BoundedPriorityDeque.hpp:438
Base class for implementing a bounded priority deque.
Definition BoundedPriorityDeque.hpp:95
void insert(const BoundingPair< K, V > &element)
Inserts an element into the _buffer.
Definition BoundedPriorityDeque.hpp:156
size_t prevIndex(size_t current) const
Provides fast access to the previous index of a given insertion position.
Definition BoundedPriorityDeque.hpp:123
BoundingPair< K, V > popBottom()
remove the lowest-priority element.
Definition BoundedPriorityDeque.hpp:326
BoundingPair< K, V > pop()
remove the highest-priority element.
Definition BoundedPriorityDeque.hpp:312
size_t size() const
Definition BoundedPriorityDeque.hpp:369
BoundingPair< K, V > top() const
Get the highest-priority element.
Definition BoundedPriorityDeque.hpp:212
void resize(size_t k)
Sets the capacity and restores clean state.
Definition BoundedPriorityDeque.hpp:398
K topK() const
Get the key from the highest-priority element.
Definition BoundedPriorityDeque.hpp:241
size_t capacity() const
Definition BoundedPriorityDeque.hpp:375
virtual bool compare(K a, K b) const =0
BoundedPriorityDequeBase(size_t capacity=0)
Primary constructor with default bounding capacity of zero.
Definition BoundedPriorityDeque.hpp:205
void push(const BoundingPair< K, V > &element)
Inserts an element into the vector.
Definition BoundedPriorityDeque.hpp:285
bool empty() const
Definition BoundedPriorityDeque.hpp:381
void clear()
clears and resets the data structure.
Definition BoundedPriorityDeque.hpp:359
void operator+=(const BoundedPriorityDequeBase< K, V > &rhs)
Merges another BoundedPriorityDeque instance into the calling instance.
Definition BoundedPriorityDeque.hpp:344
BoundingPair< K, V > bottom() const
Get the lowest-priority element.
Definition BoundedPriorityDeque.hpp:226
size_t binarySearch(const BoundingPair< K, V > &target) const
Efficiently locates the optimal insertion index.
Definition BoundedPriorityDeque.hpp:134
K bottomK() const
Get the key from the lowest-priority element.
Definition BoundedPriorityDeque.hpp:255
bool full() const
Definition BoundedPriorityDeque.hpp:387
void _popBottom()
Internal method with no return val.
Definition BoundedPriorityDeque.hpp:194
BoundingPair< K, V > operator[](size_t offsetTop) const
random accessor relative to the top of the deque.
Definition BoundedPriorityDeque.hpp:303
size_t nextIndex(size_t current) const
Provides fast access to the next index of a given insertion position.
Definition BoundedPriorityDeque.hpp:115
void emplace(K key, const V &value)
constructs a BoundingPair<K, V> element and inserts it.
Definition BoundedPriorityDeque.hpp:268
void _popTop()
Internal method with no return val.
Definition BoundedPriorityDeque.hpp:186
Lightweight custom key comparator priority dequeue.
Definition BoundedPriorityDeque.hpp:475
bool compare(K a, K b) const override
Definition BoundedPriorityDeque.hpp:479
Lightweight custom value-based comparator priority dequeue.
Definition BoundedPriorityDeque.hpp:503
bool compare(K a, K b) const override
Definition BoundedPriorityDeque.hpp:507
A template class to store key-value pairs with comparison operations.
Definition BoundedPriorityDeque.hpp:56
K key
The key of the pair.
Definition BoundedPriorityDeque.hpp:58
V value
The value of the pair.
Definition BoundedPriorityDeque.hpp:59
Type trait to detect the presence of a comparison operator in types.
Definition BoundedPriorityDeque.hpp:34