blob: 0bc94e70713c395af00ee413b57cd3c5ee843ff7 [file] [log] [blame]
James Kuszmaul97f750d2019-01-20 20:08:03 -08001#ifndef AOS_CONTAINERS_PRIORITY_QUEUE_H_
2#define AOS_CONTAINERS_PRIORITY_QUEUE_H_
3
4#include <array>
5#include <iterator>
6
7namespace 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.
18template <typename Data, size_t buffer_size, typename Compare>
19class 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
81 Data &top() { return list_[top_].data; }
82 Data &get(size_t idx) { return list_[idx].data; }
83 iterator begin() { return iterator(this, bottom_); }
84 iterator end() { return iterator(this, buffer_size); }
85
86 // Gets the index of the next (higher valued) element in the queue.
87 size_t next_idx(size_t idx) const { return list_[idx].upper_idx; }
88 // Gets the index of the previous (lower valued) element in the queue.
89 size_t prev_idx(size_t idx) const { return list_[idx].lower_idx; }
90 private:
91 struct Datum {
92 // A list element with data and the indices of the next highest/lowest
93 // priority elements.
94 Data data;
95 // Values of buffer_size indicate that we are at the beginning or end of
96 // the queue.
97 size_t lower_idx = buffer_size;
98 size_t upper_idx = buffer_size;
99 };
100
101 // Insert an element above lower_idx and below upper_idx.
102 iterator InsertInList(const Data &data, size_t lower_idx, size_t upper_idx) {
103 // For inserting new elements, when we are initially filling the queue we
104 // will increment upwards in the array; once full, we just evict the
105 // lowest priority element.
106 size_t insertion_idx = size();
107 if (full()) {
108 if (upper_idx == bottom_) {
109 // this item is lower priority than everyone else, don't insert it.
110 return end();
111 }
112 // Eject the lowest priority element.
113 insertion_idx = bottom_;
114 if (lower_idx == insertion_idx) {
115 lower_idx = buffer_size;
116 }
117 --size_;
118 bottom_ = list_[bottom_].upper_idx;
119 list_[bottom_].lower_idx = buffer_size;
120 }
121 if (upper_idx != buffer_size) {
122 list_[upper_idx].lower_idx = insertion_idx;
123 }
124 if (lower_idx != buffer_size) {
125 list_[lower_idx].upper_idx = insertion_idx;
126 }
127 if (bottom_ == upper_idx) {
128 bottom_ = insertion_idx;
129 }
130 if (top_ == lower_idx) {
131 top_ = insertion_idx;
132 }
133 list_[insertion_idx].data = data;
134 list_[insertion_idx].upper_idx = upper_idx;
135 list_[insertion_idx].lower_idx = lower_idx;
136 ++size_;
137 return iterator(this, insertion_idx);
138 }
139 ::std::array<Datum, buffer_size> list_;
140 // Index of the bottom and top of the queue.
141 size_t bottom_ = buffer_size, top_ = buffer_size;
142 // Number of elements currently in the queue.
143 size_t size_ = 0;
144 Compare cmp_;
145};
146} // namespace aos
147
148#endif // AOS_CONTAINERS_PRIORITY_QUEUE_H_