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