James Kuszmaul | 97f750d | 2019-01-20 20:08:03 -0800 | [diff] [blame] | 1 | #ifndef AOS_CONTAINERS_PRIORITY_QUEUE_H_ |
| 2 | #define AOS_CONTAINERS_PRIORITY_QUEUE_H_ |
| 3 | |
Stephan Pleines | 5fc3507 | 2024-05-22 17:33:18 -0700 | [diff] [blame] | 4 | #include <stddef.h> |
| 5 | |
James Kuszmaul | 97f750d | 2019-01-20 20:08:03 -0800 | [diff] [blame] | 6 | #include <array> |
James Kuszmaul | 2971b5a | 2023-01-29 15:49:32 -0800 | [diff] [blame] | 7 | #include <optional> |
James Kuszmaul | 97f750d | 2019-01-20 20:08:03 -0800 | [diff] [blame] | 8 | |
| 9 | namespace aos { |
| 10 | |
| 11 | // Creates a priority queue which will never exceed a particular size. |
| 12 | // Data: The type of the data to store. |
| 13 | // buffer_size: The max number of Data to store. |
| 14 | // Compare: A comparison function. If std::less were used here, then the |
| 15 | // smallest element would be discarded first, see |
| 16 | // https://en.cppreference.com/w/cpp/named_req/Compare |
| 17 | // The lowest priority elements will be discarded first to maintain buffer_size. |
| 18 | // Note that as a "queue" this is a bit incomplete because there is no mechanism |
| 19 | // to pop from the queue. |
| 20 | template <typename Data, size_t buffer_size, typename Compare> |
| 21 | class PriorityQueue { |
| 22 | public: |
| 23 | class iterator { |
| 24 | public: |
| 25 | explicit iterator(PriorityQueue *queue, size_t idx) |
| 26 | : queue_(queue), idx_(idx) {} |
| 27 | iterator &operator++() { |
| 28 | idx_ = queue_->next_idx(idx_); |
| 29 | return *this; |
| 30 | } |
| 31 | iterator operator++(int) { |
| 32 | iterator retval = *this; |
| 33 | ++(*this); |
| 34 | return retval; |
| 35 | } |
| 36 | iterator &operator--() { |
| 37 | idx_ = queue_->prev_idx(idx_); |
| 38 | return *this; |
| 39 | } |
| 40 | iterator operator--(int) { |
| 41 | iterator retval = *this; |
| 42 | --(*this); |
| 43 | return retval; |
| 44 | } |
| 45 | bool operator==(iterator other) const { |
| 46 | return queue_ == other.queue_ && idx_ == other.idx_; |
| 47 | } |
| 48 | bool operator!=(iterator other) const { return !(*this == other); } |
| 49 | Data &operator*() { return queue_->get(idx_); } |
| 50 | Data *operator->() { return &queue_->get(idx_); } |
| 51 | |
| 52 | private: |
| 53 | PriorityQueue *queue_; |
| 54 | size_t idx_; |
| 55 | }; |
| 56 | |
| 57 | constexpr PriorityQueue() {} |
| 58 | |
| 59 | // Inserts data into the queue and returns an iterator for the inserted |
| 60 | // element. If the queue was already full, then the lowest priority element |
| 61 | // will be discarded. If data is lower priority than all the current elements |
| 62 | // and the queue is full, then data is ignored and end() is returned. |
| 63 | // PushFromBottom starts the search from the bottom of the queue. |
| 64 | // TODO(james): If performance becomes an issue, improve search. |
James Kuszmaul | 2971b5a | 2023-01-29 15:49:32 -0800 | [diff] [blame] | 65 | iterator PushFromBottom(Data data) { |
James Kuszmaul | 97f750d | 2019-01-20 20:08:03 -0800 | [diff] [blame] | 66 | size_t lower_idx = buffer_size; |
| 67 | size_t upper_idx = bottom_; |
| 68 | // Find our spot in the queue: |
James Kuszmaul | 2971b5a | 2023-01-29 15:49:32 -0800 | [diff] [blame] | 69 | while (upper_idx != buffer_size && |
| 70 | !cmp_(data, list_[upper_idx].data.value())) { |
James Kuszmaul | 97f750d | 2019-01-20 20:08:03 -0800 | [diff] [blame] | 71 | lower_idx = upper_idx; |
| 72 | upper_idx = list_[upper_idx].upper_idx; |
| 73 | if (upper_idx == buffer_size) { |
| 74 | break; |
| 75 | } |
| 76 | } |
James Kuszmaul | 2971b5a | 2023-01-29 15:49:32 -0800 | [diff] [blame] | 77 | return InsertInList(std::move(data), lower_idx, upper_idx); |
James Kuszmaul | 97f750d | 2019-01-20 20:08:03 -0800 | [diff] [blame] | 78 | } |
| 79 | |
| 80 | size_t size() const { return size_; } |
| 81 | bool empty() const { return size_ == 0; } |
| 82 | bool full() const { return size_ == buffer_size; } |
| 83 | |
James Kuszmaul | 4b04eaf | 2019-02-10 17:13:25 -0800 | [diff] [blame] | 84 | // Removes all the elements from the queue: |
| 85 | void clear() { |
| 86 | size_ = 0; |
| 87 | bottom_ = buffer_size; |
| 88 | top_ = buffer_size; |
| 89 | } |
| 90 | |
James Kuszmaul | 2971b5a | 2023-01-29 15:49:32 -0800 | [diff] [blame] | 91 | Data &top() { return list_[top_].data.value(); } |
| 92 | const Data &top() const { return list_[top_].data.value(); } |
| 93 | Data &get(size_t idx) { return list_[idx].data.value(); } |
| 94 | const Data &get(size_t idx) const { return list_[idx].data.value(); } |
James Kuszmaul | 97f750d | 2019-01-20 20:08:03 -0800 | [diff] [blame] | 95 | iterator begin() { return iterator(this, bottom_); } |
| 96 | iterator end() { return iterator(this, buffer_size); } |
| 97 | |
| 98 | // Gets the index of the next (higher valued) element in the queue. |
| 99 | size_t next_idx(size_t idx) const { return list_[idx].upper_idx; } |
| 100 | // Gets the index of the previous (lower valued) element in the queue. |
| 101 | size_t prev_idx(size_t idx) const { return list_[idx].lower_idx; } |
Austin Schuh | 60e7794 | 2022-05-16 17:48:24 -0700 | [diff] [blame] | 102 | |
James Kuszmaul | 97f750d | 2019-01-20 20:08:03 -0800 | [diff] [blame] | 103 | private: |
| 104 | struct Datum { |
| 105 | // A list element with data and the indices of the next highest/lowest |
| 106 | // priority elements. |
James Kuszmaul | 2971b5a | 2023-01-29 15:49:32 -0800 | [diff] [blame] | 107 | std::optional<Data> data; |
James Kuszmaul | 97f750d | 2019-01-20 20:08:03 -0800 | [diff] [blame] | 108 | // Values of buffer_size indicate that we are at the beginning or end of |
| 109 | // the queue. |
| 110 | size_t lower_idx = buffer_size; |
| 111 | size_t upper_idx = buffer_size; |
| 112 | }; |
| 113 | |
| 114 | // Insert an element above lower_idx and below upper_idx. |
James Kuszmaul | 2971b5a | 2023-01-29 15:49:32 -0800 | [diff] [blame] | 115 | iterator InsertInList(Data data, size_t lower_idx, size_t upper_idx) { |
James Kuszmaul | 97f750d | 2019-01-20 20:08:03 -0800 | [diff] [blame] | 116 | // For inserting new elements, when we are initially filling the queue we |
| 117 | // will increment upwards in the array; once full, we just evict the |
| 118 | // lowest priority element. |
| 119 | size_t insertion_idx = size(); |
| 120 | if (full()) { |
| 121 | if (upper_idx == bottom_) { |
| 122 | // this item is lower priority than everyone else, don't insert it. |
| 123 | return end(); |
| 124 | } |
| 125 | // Eject the lowest priority element. |
| 126 | insertion_idx = bottom_; |
| 127 | if (lower_idx == insertion_idx) { |
| 128 | lower_idx = buffer_size; |
| 129 | } |
| 130 | --size_; |
| 131 | bottom_ = list_[bottom_].upper_idx; |
| 132 | list_[bottom_].lower_idx = buffer_size; |
| 133 | } |
| 134 | if (upper_idx != buffer_size) { |
| 135 | list_[upper_idx].lower_idx = insertion_idx; |
| 136 | } |
| 137 | if (lower_idx != buffer_size) { |
| 138 | list_[lower_idx].upper_idx = insertion_idx; |
| 139 | } |
| 140 | if (bottom_ == upper_idx) { |
| 141 | bottom_ = insertion_idx; |
| 142 | } |
| 143 | if (top_ == lower_idx) { |
| 144 | top_ = insertion_idx; |
| 145 | } |
James Kuszmaul | 2971b5a | 2023-01-29 15:49:32 -0800 | [diff] [blame] | 146 | list_[insertion_idx].data.emplace(std::move(data)); |
James Kuszmaul | 97f750d | 2019-01-20 20:08:03 -0800 | [diff] [blame] | 147 | list_[insertion_idx].upper_idx = upper_idx; |
| 148 | list_[insertion_idx].lower_idx = lower_idx; |
| 149 | ++size_; |
| 150 | return iterator(this, insertion_idx); |
| 151 | } |
| 152 | ::std::array<Datum, buffer_size> list_; |
| 153 | // Index of the bottom and top of the queue. |
| 154 | size_t bottom_ = buffer_size, top_ = buffer_size; |
| 155 | // Number of elements currently in the queue. |
| 156 | size_t size_ = 0; |
| 157 | Compare cmp_; |
| 158 | }; |
| 159 | } // namespace aos |
| 160 | |
| 161 | #endif // AOS_CONTAINERS_PRIORITY_QUEUE_H_ |