#include "../common.h" #include "../php/iterators/php_priority_queue_iterator.h" #include "../php/handlers/php_priority_queue_handlers.h" #include "../php/classes/php_priority_queue_ce.h" #include "ds_priority_queue.h" #define LEFT(x) (((x) * 2) + 1) #define RIGHT(x) (((x) * 2) + 2) #define PARENT(x) (((x) - 1) / 2) // Insertion stamp, for equal priority comparison fallback. #define STAMP(n) (Z_NEXT((n)->value)) static inline uint32_t ds_priority_queue_get_capacity_for_size(uint32_t size) { return ds_next_power_of_2(size, DS_PRIORITY_QUEUE_MIN_CAPACITY); } // Priority comparison, with insertion stamp fallback. static inline int ds_priority_queue_node_compare( ds_priority_queue_node_t *a, ds_priority_queue_node_t *b ) { zval retval; if (compare_function(&retval, &a->priority, &b->priority) == SUCCESS) { int result = (int) zval_get_long(&retval); // If the priorities are equal, use the insertion stamp as a tie-break. if (result == 0) { return (STAMP(a) < STAMP(b) ? 1 : -1); } return result; } else { // Not sure what to do when the compare function fails. return 0; } } static inline ds_priority_queue_node_t *reallocate_nodes(ds_priority_queue_node_t *nodes, uint32_t capacity) { return erealloc(nodes, capacity * sizeof(ds_priority_queue_node_t)); } static inline ds_priority_queue_node_t *allocate_nodes(uint32_t capacity) { return ecalloc(capacity, sizeof(ds_priority_queue_node_t)); } static inline void reallocate_to_capacity(ds_priority_queue_t *queue, uint32_t capacity) { queue->nodes = reallocate_nodes(queue->nodes, capacity); queue->capacity = capacity; } static inline void increase_capacity(ds_priority_queue_t *queue) { reallocate_to_capacity(queue, queue->capacity * 2); } void ds_priority_queue_allocate(ds_priority_queue_t *queue, uint32_t capacity) { capacity = ds_priority_queue_get_capacity_for_size(capacity); if (capacity > queue->capacity) { reallocate_to_capacity(queue, capacity); } } ds_priority_queue_t *ds_priority_queue() { ds_priority_queue_t *queue = ecalloc(1, sizeof(ds_priority_queue_t)); queue->nodes = allocate_nodes(DS_PRIORITY_QUEUE_MIN_CAPACITY); queue->capacity = DS_PRIORITY_QUEUE_MIN_CAPACITY; queue->size = 0; queue->next = 0; return queue; } uint32_t ds_priority_queue_capacity(ds_priority_queue_t *queue) { return queue->capacity; } void ds_priority_queue_push(ds_priority_queue_t *queue, zval *value, zval *priority) { zval comparison; uint32_t parent; uint32_t index; ds_priority_queue_node_t *nodes; ds_priority_queue_node_t *node; if (queue->size == queue->capacity) { increase_capacity(queue); } nodes = queue->nodes; for (index = queue->size; index > 0; index = parent) { // Move up the heap parent = PARENT(index); // If the parent node's priority is less than or equal to the inserted, // the heap is valid and we can break. if (compare_function(&comparison, priority, &nodes[parent].priority) == SUCCESS) { if (((int) zval_get_long(&comparison)) < 1) { break; } } else { // Not sure what to do if the compare function fails. return; } nodes[index] = nodes[parent]; } node = &queue->nodes[index]; // Initialize the new node STAMP(node) = ++queue->next; ZVAL_COPY(&node->value, value); ZVAL_COPY(&node->priority, priority); queue->size++; } static inline void ds_priority_queue_compact(ds_priority_queue_t *queue) { if (queue->size <= (queue->capacity / 4) && (queue->capacity / 2) >= DS_PRIORITY_QUEUE_MIN_CAPACITY) { reallocate_to_capacity(queue, queue->capacity / 2); } } void ds_priority_queue_pop(ds_priority_queue_t *queue, zval *return_value) { uint32_t index; uint32_t swap; ds_priority_queue_node_t bottom; ds_priority_queue_node_t *nodes = queue->nodes; const uint32_t size = queue->size; const uint32_t half = (size - 1) / 2; // Guard against pop when the queue is empty. if (size == 0) { NOT_ALLOWED_WHEN_EMPTY(); ZVAL_NULL(return_value); return; } // Return the root if a return value was requested. if (return_value) { ZVAL_COPY(return_value, &(nodes[0].value)); } // Grab the last node in the queue, which should have the lowest priority. bottom = nodes[size - 1]; // Destruct the root, because we're removing it from the queue. DTOR_AND_UNDEF(&(nodes[0].value)); DTOR_AND_UNDEF(&(nodes[0].priority)); queue->size--; for (index = 0; index < half; index = swap) { swap = LEFT(index); // If the right leaf is smaller than the left, swap right instead. if (swap < queue->size && ds_priority_queue_node_compare(&nodes[swap], &nodes[swap + 1]) < 0) { swap++; } // Heap constraints are preserved when the if (ds_priority_queue_node_compare(&nodes[swap], &bottom) < 0) { break; } nodes[index] = nodes[swap]; } nodes[index] = bottom; // Reduce the size of the buffer if the size has dropped below a threshold. ds_priority_queue_compact(queue); } static ds_priority_queue_node_t *copy_nodes(ds_priority_queue_t *queue) { ds_priority_queue_node_t *copies = allocate_nodes(queue->capacity); ds_priority_queue_node_t *src = queue->nodes; ds_priority_queue_node_t *end = queue->nodes + queue->size; ds_priority_queue_node_t *dst = copies; for (; src < end; ++src, ++dst) { ZVAL_COPY(&dst->value, &src->value); // This also copies insertion stamp. ZVAL_COPY(&dst->priority, &src->priority); } return copies; } ds_priority_queue_t *ds_priority_queue_clone(ds_priority_queue_t * queue) { ds_priority_queue_t *clone = ecalloc(1, sizeof(ds_priority_queue_t)); clone->nodes = copy_nodes(queue); clone->capacity = queue->capacity; clone->size = queue->size; clone->next = queue->next; return clone; } zval *ds_priority_queue_peek(ds_priority_queue_t *queue) { if (queue->size == 0) { NOT_ALLOWED_WHEN_EMPTY(); return NULL; } return &queue->nodes[0].value; } static int priority_sort(const void *a, const void *b) { return ds_priority_queue_node_compare( (ds_priority_queue_node_t *) b, (ds_priority_queue_node_t *) a ); } ds_priority_queue_node_t* ds_priority_queue_create_sorted_buffer(ds_priority_queue_t *queue) { ds_priority_queue_node_t *buffer = allocate_nodes(queue->size); memcpy(buffer, queue->nodes, queue->size * sizeof(ds_priority_queue_node_t)); qsort(buffer, queue->size, sizeof(ds_priority_queue_node_t), priority_sort); return buffer; } void ds_priority_queue_to_array(ds_priority_queue_t *queue, zval *array) { if (DS_PRIORITY_QUEUE_IS_EMPTY(queue)) { array_init(array); } else { ds_priority_queue_node_t *pos, *end, *buf; buf = ds_priority_queue_create_sorted_buffer(queue); pos = buf; end = buf + queue->size; array_init_size(array, queue->size); for (; pos < end; ++pos) { add_next_index_zval(array, &pos->value); Z_TRY_ADDREF_P(&pos->value); } efree(buf); } } void ds_priority_queue_clear(ds_priority_queue_t *queue) { ds_priority_queue_node_t *pos = queue->nodes; ds_priority_queue_node_t *end = queue->nodes + queue->size; for (; pos < end; ++pos) { DTOR_AND_UNDEF(&pos->value); DTOR_AND_UNDEF(&pos->priority); } queue->size = 0; reallocate_to_capacity(queue, DS_PRIORITY_QUEUE_MIN_CAPACITY); } void ds_priority_queue_free(ds_priority_queue_t *queue) { ds_priority_queue_clear(queue); efree(queue->nodes); efree(queue); }