xref: /ext-ds/src/ds/ds_priority_queue.c (revision 02455912)
1 #include "../common.h"
2 
3 #include "../php/iterators/php_priority_queue_iterator.h"
4 #include "../php/handlers/php_priority_queue_handlers.h"
5 #include "../php/classes/php_priority_queue_ce.h"
6 
7 #include "ds_priority_queue.h"
8 
9 #define LEFT(x)   (((x) * 2) + 1)
10 #define RIGHT(x)  (((x) * 2) + 2)
11 #define PARENT(x) (((x) - 1) / 2)
12 
13 // Insertion stamp, for equal priority comparison fallback.
14 #define STAMP(n) (Z_NEXT((n)->value))
15 
ds_priority_queue_get_capacity_for_size(uint32_t size)16 static inline uint32_t ds_priority_queue_get_capacity_for_size(uint32_t size)
17 {
18     return ds_next_power_of_2(size, DS_PRIORITY_QUEUE_MIN_CAPACITY);
19 }
20 
21 // Priority comparison, with insertion stamp fallback.
ds_priority_queue_node_compare(ds_priority_queue_node_t * a,ds_priority_queue_node_t * b)22 static inline int ds_priority_queue_node_compare(
23     ds_priority_queue_node_t *a,
24     ds_priority_queue_node_t *b
25 ) {
26     zval retval;
27 
28     if (compare_function(&retval, &a->priority, &b->priority) == SUCCESS) {
29         int result = (int) zval_get_long(&retval);
30 
31         // If the priorities are equal, use the insertion stamp as a tie-break.
32         if (result == 0) {
33             return (STAMP(a) < STAMP(b) ? 1 : -1);
34         }
35 
36         return result;
37 
38     } else {
39         // Not sure what to do when the compare function fails.
40         return 0;
41     }
42 }
43 
reallocate_nodes(ds_priority_queue_node_t * nodes,uint32_t capacity)44 static inline ds_priority_queue_node_t *reallocate_nodes(ds_priority_queue_node_t *nodes, uint32_t capacity)
45 {
46     return erealloc(nodes, capacity * sizeof(ds_priority_queue_node_t));
47 }
48 
allocate_nodes(uint32_t capacity)49 static inline ds_priority_queue_node_t *allocate_nodes(uint32_t capacity)
50 {
51     return ecalloc(capacity, sizeof(ds_priority_queue_node_t));
52 }
53 
reallocate_to_capacity(ds_priority_queue_t * queue,uint32_t capacity)54 static inline void reallocate_to_capacity(ds_priority_queue_t *queue, uint32_t capacity)
55 {
56     queue->nodes = reallocate_nodes(queue->nodes, capacity);
57     queue->capacity = capacity;
58 }
59 
increase_capacity(ds_priority_queue_t * queue)60 static inline void increase_capacity(ds_priority_queue_t *queue)
61 {
62     reallocate_to_capacity(queue, queue->capacity * 2);
63 }
64 
ds_priority_queue_allocate(ds_priority_queue_t * queue,uint32_t capacity)65 void ds_priority_queue_allocate(ds_priority_queue_t *queue, uint32_t capacity)
66 {
67     capacity = ds_priority_queue_get_capacity_for_size(capacity);
68 
69     if (capacity > queue->capacity) {
70         reallocate_to_capacity(queue, capacity);
71     }
72 }
73 
ds_priority_queue()74 ds_priority_queue_t *ds_priority_queue()
75 {
76     ds_priority_queue_t *queue = ecalloc(1, sizeof(ds_priority_queue_t));
77 
78     queue->nodes    = allocate_nodes(DS_PRIORITY_QUEUE_MIN_CAPACITY);
79     queue->capacity = DS_PRIORITY_QUEUE_MIN_CAPACITY;
80     queue->size     = 0;
81     queue->next     = 0;
82 
83     return queue;
84 }
85 
ds_priority_queue_capacity(ds_priority_queue_t * queue)86 uint32_t ds_priority_queue_capacity(ds_priority_queue_t *queue)
87 {
88     return queue->capacity;
89 }
90 
ds_priority_queue_push(ds_priority_queue_t * queue,zval * value,zval * priority)91 void ds_priority_queue_push(ds_priority_queue_t *queue, zval *value, zval *priority)
92 {
93     zval comparison;
94 
95     uint32_t parent;
96     uint32_t index;
97 
98     ds_priority_queue_node_t *nodes;
99     ds_priority_queue_node_t *node;
100 
101     if (queue->size == queue->capacity) {
102         increase_capacity(queue);
103     }
104 
105     nodes = queue->nodes;
106 
107     for (index = queue->size; index > 0; index = parent) {
108 
109         // Move up the heap
110         parent = PARENT(index);
111 
112         // If the parent node's priority is less than or equal to the inserted,
113         // the heap is valid and we can break.
114         if (compare_function(&comparison, priority, &nodes[parent].priority) == SUCCESS) {
115             if (((int) zval_get_long(&comparison)) < 1) {
116                 break;
117             }
118         } else {
119             // Not sure what to do if the compare function fails.
120             return;
121         }
122 
123         nodes[index] = nodes[parent];
124     }
125 
126     node = &queue->nodes[index];
127 
128     // Initialize the new node
129     STAMP(node) = ++queue->next;
130 
131     ZVAL_COPY(&node->value, value);
132     ZVAL_COPY(&node->priority, priority);
133 
134     queue->size++;
135 }
136 
ds_priority_queue_compact(ds_priority_queue_t * queue)137 static inline void ds_priority_queue_compact(ds_priority_queue_t *queue)
138 {
139     if (queue->size <= (queue->capacity / 4) && (queue->capacity / 2) >= DS_PRIORITY_QUEUE_MIN_CAPACITY) {
140         reallocate_to_capacity(queue, queue->capacity / 2);
141     }
142 }
143 
ds_priority_queue_pop(ds_priority_queue_t * queue,zval * return_value)144 void ds_priority_queue_pop(ds_priority_queue_t *queue, zval *return_value)
145 {
146     uint32_t index;
147     uint32_t swap;
148 
149     ds_priority_queue_node_t bottom;
150     ds_priority_queue_node_t *nodes = queue->nodes;
151 
152     const uint32_t size = queue->size;
153     const uint32_t half = (size - 1) / 2;
154 
155     // Guard against pop when the queue is empty.
156     if (size == 0) {
157         NOT_ALLOWED_WHEN_EMPTY();
158         ZVAL_NULL(return_value);
159         return;
160     }
161 
162     // Return the root if a return value was requested.
163     if (return_value) {
164         ZVAL_COPY(return_value, &(nodes[0].value));
165     }
166 
167     // Grab the last node in the queue, which should have the lowest priority.
168     bottom = nodes[size - 1];
169 
170     // Destruct the root, because we're removing it from the queue.
171     DTOR_AND_UNDEF(&(nodes[0].value));
172     DTOR_AND_UNDEF(&(nodes[0].priority));
173 
174     queue->size--;
175 
176     for (index = 0; index < half; index = swap) {
177         swap = LEFT(index);
178 
179         // If the right leaf is smaller than the left, swap right instead.
180         if (swap < queue->size && ds_priority_queue_node_compare(&nodes[swap], &nodes[swap + 1]) < 0) {
181             swap++;
182         }
183 
184         // Heap constraints are preserved when the
185         if (ds_priority_queue_node_compare(&nodes[swap], &bottom) < 0) {
186             break;
187         }
188 
189         nodes[index] = nodes[swap];
190     }
191 
192     nodes[index] = bottom;
193 
194     // Reduce the size of the buffer if the size has dropped below a threshold.
195     ds_priority_queue_compact(queue);
196 }
197 
copy_nodes(ds_priority_queue_t * queue)198 static ds_priority_queue_node_t *copy_nodes(ds_priority_queue_t *queue)
199 {
200     ds_priority_queue_node_t *copies = allocate_nodes(queue->capacity);
201 
202     ds_priority_queue_node_t *src = queue->nodes;
203     ds_priority_queue_node_t *end = queue->nodes + queue->size;
204     ds_priority_queue_node_t *dst = copies;
205 
206     for (; src < end; ++src, ++dst) {
207         ZVAL_COPY(&dst->value, &src->value); // This also copies insertion stamp.
208         ZVAL_COPY(&dst->priority, &src->priority);
209     }
210 
211     return copies;
212 }
213 
ds_priority_queue_clone(ds_priority_queue_t * queue)214 ds_priority_queue_t *ds_priority_queue_clone(ds_priority_queue_t * queue)
215 {
216     ds_priority_queue_t *clone = ecalloc(1, sizeof(ds_priority_queue_t));
217 
218     clone->nodes    = copy_nodes(queue);
219     clone->capacity = queue->capacity;
220     clone->size     = queue->size;
221     clone->next     = queue->next;
222 
223     return clone;
224 }
225 
ds_priority_queue_peek(ds_priority_queue_t * queue)226 zval *ds_priority_queue_peek(ds_priority_queue_t *queue)
227 {
228     if (queue->size == 0) {
229         NOT_ALLOWED_WHEN_EMPTY();
230         return NULL;
231     }
232 
233     return &queue->nodes[0].value;
234 }
235 
priority_sort(const void * a,const void * b)236 static int priority_sort(const void *a, const void *b)
237 {
238     return ds_priority_queue_node_compare(
239         (ds_priority_queue_node_t *) b,
240         (ds_priority_queue_node_t *) a
241     );
242 }
243 
ds_priority_queue_create_sorted_buffer(ds_priority_queue_t * queue)244 ds_priority_queue_node_t* ds_priority_queue_create_sorted_buffer(ds_priority_queue_t *queue)
245 {
246     ds_priority_queue_node_t *buffer = allocate_nodes(queue->size);
247 
248     memcpy(buffer, queue->nodes, queue->size * sizeof(ds_priority_queue_node_t));
249     qsort(buffer, queue->size, sizeof(ds_priority_queue_node_t), priority_sort);
250 
251     return buffer;
252 }
253 
ds_priority_queue_to_array(ds_priority_queue_t * queue,zval * array)254 void ds_priority_queue_to_array(ds_priority_queue_t *queue, zval *array)
255 {
256     if (DS_PRIORITY_QUEUE_IS_EMPTY(queue)) {
257         array_init(array);
258 
259     } else {
260         ds_priority_queue_node_t *pos, *end, *buf;
261 
262         buf = ds_priority_queue_create_sorted_buffer(queue);
263         pos = buf;
264         end = buf + queue->size;
265 
266         array_init_size(array, queue->size);
267 
268         for (; pos < end; ++pos) {
269             add_next_index_zval(array, &pos->value);
270             Z_TRY_ADDREF_P(&pos->value);
271         }
272 
273         efree(buf);
274     }
275 }
276 
ds_priority_queue_clear(ds_priority_queue_t * queue)277 void ds_priority_queue_clear(ds_priority_queue_t *queue)
278 {
279     ds_priority_queue_node_t *pos = queue->nodes;
280     ds_priority_queue_node_t *end = queue->nodes + queue->size;
281 
282     for (; pos < end; ++pos) {
283         DTOR_AND_UNDEF(&pos->value);
284         DTOR_AND_UNDEF(&pos->priority);
285     }
286 
287     queue->size = 0;
288 
289     reallocate_to_capacity(queue, DS_PRIORITY_QUEUE_MIN_CAPACITY);
290 }
291 
ds_priority_queue_free(ds_priority_queue_t * queue)292 void ds_priority_queue_free(ds_priority_queue_t *queue)
293 {
294     ds_priority_queue_clear(queue);
295     efree(queue->nodes);
296     efree(queue);
297 }
298