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