Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 93 additions & 27 deletions dttools/src/priority_queue.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,43 @@ See the file COPYING for details.
*/

#include "priority_queue.h"
#include "debug.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <float.h>
#include <stdarg.h>
#include <stddef.h>

#define DEFAULT_CAPACITY 127

/* This is the maximum number of priorities defined in struct element. */
#define MAX_PRIORITY_COUNT 3

struct element {
void *data;
double priority; // In this implementation, elements with bigger priorities are considered to be privileged.

// Priorities are stored as three separate variables, compared in order.
// For two elements e1 and e2, e1 is considered to have higher priority than e2 if
// e1->priority_0 > e2->priority_0 or e1->priority_0 == e2->priority_0 and e1->priority_1 > e2->priority_1 or ...
// Larger numbers have higher priority.

// WARNING: This attributes should be the last defined in this struct.
double priority_0;
double priority_1;
double priority_2;
};

struct priority_queue {
int size;
int capacity;
struct element **elements;

int priority_count; // Number of priorities per element. Priorities are compared
// in the order priority0 -> priority1 -> ...

/* The following three cursors are used to iterate over the elements in the numerical order they are stored in the array, which is
different from the order of priorities. Each of them has different concerns when traverse the queue Though the typical priority-based
traversal is done by the repeated invocation of priority_queue_peek_top and priority_queue_pop APIs, rather than using any cursors. */
Expand All @@ -33,6 +51,33 @@ struct priority_queue {
};

/****** Static Methods ******/
static int cmp_left_right(struct priority_queue *pq, int left_idx, int right_idx)
{
struct element *left = pq->elements[left_idx];
struct element *right = pq->elements[right_idx];

// Compare priorities in order
for (int i = 0; i < pq->priority_count; i++) {
double left_priority = *((double *)((char *)left + offsetof(struct element, priority_0) + i * sizeof(double)));
double right_priority = *((double *)((char *)right + offsetof(struct element, priority_0) + i * sizeof(double)));

if (left_priority < right_priority) {
return -1;
}
if (left_priority > right_priority) {
return 1;
}
}

// If all priorities are equal, return 0
return 0;
}

#define LE(p, l, r) (cmp_left_right(p, l, r) <= 0)
#define LT(p, l, r) (cmp_left_right(p, l, r) < 0)
#define GT(p, l, r) (cmp_left_right(p, l, r) > 0)
#define GE(p, l, r) (cmp_left_right(p, l, r) >= 0)
#define EQ(p, l, r) (cmp_left_right(p, l, r) == 0)

static void swap_elements(struct priority_queue *pq, int i, int j)
{
Expand All @@ -47,7 +92,7 @@ static int swim(struct priority_queue *pq, int k)
return 1;
}

while (k > 0 && pq->elements[(k - 1) / 2]->priority <= pq->elements[k]->priority) {
while (k > 0 && LE(pq, (k - 1) / 2, k)) {
swap_elements(pq, k, (k - 1) / 2);
k = (k - 1) / 2;
}
Expand All @@ -63,12 +108,14 @@ static int sink(struct priority_queue *pq, int k)

while (2 * k + 1 < pq->size) {
int j = 2 * k + 1;
if (j + 1 < pq->size && pq->elements[j]->priority <= pq->elements[j + 1]->priority) {
if (j + 1 < pq->size && LE(pq, j, j + 1)) {
j++;
}
if (pq->elements[k]->priority >= pq->elements[j]->priority) {

if (GE(pq, k, j)) {
break;
}

swap_elements(pq, k, j);
k = j;
}
Expand Down Expand Up @@ -99,7 +146,7 @@ static int priority_queue_double_capacity(struct priority_queue *pq)

/****** External Methods ******/

struct priority_queue *priority_queue_create(int init_capacity)
struct priority_queue *priority_queue_create(int init_capacity, int priority_count)
{
struct priority_queue *pq = (struct priority_queue *)malloc(sizeof(struct priority_queue));
if (!pq) {
Expand All @@ -110,17 +157,22 @@ struct priority_queue *priority_queue_create(int init_capacity)
init_capacity = DEFAULT_CAPACITY;
}

if (priority_count < 1 || priority_count > MAX_PRIORITY_COUNT) {
fatal("Priority count must be at least 1 and at most %d.\n", MAX_PRIORITY_COUNT);
return NULL;
}

pq->elements = (struct element **)calloc(init_capacity, sizeof(struct element *));
if (!pq->elements) {
free(pq);
fprintf(stderr, "Fatal error: Memory allocation failed.\n");
exit(EXIT_FAILURE);
fatal("Priority queue memory allocation failed.\n");
return NULL;
}

pq->capacity = init_capacity;
pq->size = 0;

pq->priority_count = priority_count;

pq->static_cursor = 0;
pq->base_cursor = 0;
pq->rotate_cursor = 0;
Expand All @@ -137,7 +189,7 @@ int priority_queue_size(struct priority_queue *pq)
return pq->size;
}

int priority_queue_push(struct priority_queue *pq, void *data, double priority)
int priority_queue_push(struct priority_queue *pq, void *data, ...)
{
if (!pq) {
return -1;
Expand All @@ -148,12 +200,21 @@ int priority_queue_push(struct priority_queue *pq, void *data, double priority)
return -1;
}
}
struct element *e = (struct element *)malloc(sizeof(struct element));

struct element *e = (struct element *)calloc(1, sizeof(struct element));
if (!e) {
return -1;
}

e->data = data;
e->priority = priority;

// Collect variable arguments for the three priorities
va_list args;
va_start(args, data);
for (int i = 0; i < pq->priority_count; i++) {
*((double *)((char *)e + offsetof(struct element, priority_0) + i * sizeof(double))) = va_arg(args, double);
}
va_end(args);

pq->elements[pq->size++] = e;

Expand Down Expand Up @@ -192,13 +253,14 @@ void *priority_queue_peek_top(struct priority_queue *pq)
return pq->elements[0]->data;
}

double priority_queue_get_priority_at(struct priority_queue *pq, int idx)
double priority_queue_get_priority_at(struct priority_queue *pq, int priority_idx, int element_index)
{
if (!pq || pq->size < 1 || idx < 0 || idx > pq->size - 1) {
if (!pq || pq->size < 1 || element_index < 0 || element_index > pq->size - 1 || priority_idx < 0 || priority_idx >= pq->priority_count) {
return 0;
}

return pq->elements[idx]->priority;
struct element *e = pq->elements[element_index];
return *((double *)((char *)e + offsetof(struct element, priority_0) + priority_idx * sizeof(double)));
}

double priority_queue_get_top_priority(struct priority_queue *pq)
Expand All @@ -207,7 +269,8 @@ double priority_queue_get_top_priority(struct priority_queue *pq)
return 0;
}

return pq->elements[0]->priority;
struct element *e = pq->elements[0];
return *((double *)((char *)e + offsetof(struct element, priority_0) + 0 * sizeof(double)));
}

void *priority_queue_peek_at(struct priority_queue *pq, int idx)
Expand All @@ -219,7 +282,7 @@ void *priority_queue_peek_at(struct priority_queue *pq, int idx)
return pq->elements[idx]->data;
}

int priority_queue_update_priority(struct priority_queue *pq, void *data, double new_priority)
int priority_queue_update_priority(struct priority_queue *pq, void *data, int priority_idx, double new_priority)
{
if (!pq) {
return -1;
Expand All @@ -233,13 +296,19 @@ int priority_queue_update_priority(struct priority_queue *pq, void *data, double
}
}

/* If the data isn’t already in the queue, enqueue it. */
if (idx == -1) {
return priority_queue_push(pq, data, new_priority);
return -1;
}

double old_priority = pq->elements[idx]->priority;
pq->elements[idx]->priority = new_priority;
struct element *e = pq->elements[idx];

// Get old priority and set new priority based on priority_idx
if (priority_idx < 0 || priority_idx >= pq->priority_count) {
return -1;
}

double old_priority = *((double *)((char *)e + offsetof(struct element, priority_0) + priority_idx * sizeof(double)));
*((double *)((char *)e + offsetof(struct element, priority_0) + priority_idx * sizeof(double))) = new_priority;

int new_idx = -1;

Expand Down Expand Up @@ -346,19 +415,14 @@ int priority_queue_remove(struct priority_queue *pq, int idx)
struct element *to_delete = pq->elements[idx];
struct element *last_elem = pq->elements[pq->size - 1];

double old_priority = to_delete->priority;
double new_priority = last_elem->priority;

free(to_delete);

pq->size--;
if (idx != pq->size) {
pq->elements[idx] = last_elem;
pq->elements[pq->size] = NULL;

if (new_priority > old_priority) {
if (GT(pq, idx, pq->size - 1)) {
swim(pq, idx);
} else if (new_priority < old_priority) {
} else if (LT(pq, idx, pq->size - 1)) {
sink(pq, idx);
}
} else {
Expand All @@ -380,6 +444,8 @@ int priority_queue_remove(struct priority_queue *pq, int idx)
priority_queue_rotate_reset(pq);
}

free(to_delete);

return 1;
}

Expand Down
37 changes: 27 additions & 10 deletions dttools/src/priority_queue.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,28 @@ The fifth time -
[(5, "e"), (4, "d"), (2, "b"), (1, "a"), (3, "c")]
As seen, the iteration order of elements is not the same as the priority order.

Further, the priority queue can be used to store elements with multiple priorities.
Each element can have an array of priorities, allowing for multi-level priority ordering.
Priorities are compared lexicographically: priority[0] is compared first; if equal, priority[1]
is compared; if equal, priority[2] is compared, and so on. This allows for sophisticated
tie-breaking schemes where higher-indexed priorities serve as secondary criteria.

For example, with 3 priority levels:
- Element A with priorities [5.0, 3.0, 7.0]
- Element B with priorities [5.0, 4.0, 1.0]
The comparison checks priority[0]: both are 5.0 (equal), so it moves to priority[1].
Since 3.0 < 4.0, element B has higher overall priority. Note that priority[2] is never examined.

An example to create a priority queue and manipulate its elements:
<pre>
struct priority_queue *pq;
pq = priority_queue_create(10);
pq = priority_queue_create(10, 2); // intial space for 10 elements and 2 priorities per element

int priority = 5;
int priority0 = 5;
int priority1 = 3;
void *data = someDataPointer;

priority_queue_push(pq, data, priority);
priority_queue_push(pq, data, priority0, priority1);
data = priority_queue_pop(pq);
void *headData = priority_queue_peek_top(pq);
</pre>
Expand Down Expand Up @@ -92,9 +105,10 @@ PRIORITY_QUEUE_BASE_ITERATE (pq, idx, data, iter_count, iter_depth) {
/** Create a new priority queue.
Element with a higher priority is at the top of the heap.
@param init_capacity The initial number of elements in the queue. If zero, a default value will be used.
@param priority_count The number of priorities per element.
@return A pointer to a new priority queue.
*/
struct priority_queue *priority_queue_create(int init_capacity);
struct priority_queue *priority_queue_create(int init_capacity, int priority_count);

/** Count the elements in a priority queue.
@param pq A pointer to a priority queue.
Expand All @@ -103,13 +117,14 @@ struct priority_queue *priority_queue_create(int init_capacity);
int priority_queue_size(struct priority_queue *pq);

/** Push an element into a priority queue.
The standard push operation. New elements are placed lower than existing elements of the same priority
The standard push operation. New elements are placed lower than existing elements of the same priority.
Takes three priority values as variable arguments.
@param pq A pointer to a priority queue.
@param data A pointer to store in the queue.
@param priority The specified priority with the given object.
@param ... The priority values (priority_0, priority_1, ...) as doubles.
@return The idex of data if the push succeeded, -1 on failure.
*/
int priority_queue_push(struct priority_queue *pq, void *data, double priority);
int priority_queue_push(struct priority_queue *pq, void *data, ...);

/** Pop the element with the highest priority from a priority queue.
@param pq A pointer to a priority queue.
Expand All @@ -134,10 +149,11 @@ void *priority_queue_peek_at(struct priority_queue *pq, int index);

/** Get the priority of an element at a specified index.
@param pq A pointer to a priority queue.
@param index The index of the element.
@param priority_idx The index of the priority.
@param element_index The index of the element.
@return The priority of the element if any, NAN on failure.
*/
double priority_queue_get_priority_at(struct priority_queue *pq, int index);
double priority_queue_get_priority_at(struct priority_queue *pq, int priority_idx, int element_index);

/** Get the priority of the top element in a priority queue.
@param pq A pointer to a priority queue.
Expand All @@ -148,10 +164,11 @@ double priority_queue_get_top_priority(struct priority_queue *pq);
/** Update the priority of an element in a priority queue.
@param pq A pointer to a priority queue.
@param data The pointer to the element to update.
@param priority_idx The index of the priority to update.
@param new_priority The new priority of the element.
@return The new index if the update succeeded, -1 on failure.
*/
int priority_queue_update_priority(struct priority_queue *pq, void *data, double new_priority);
int priority_queue_update_priority(struct priority_queue *pq, void *data, int priority_idx, double new_priority);

/** Find the index of an element in a priority queue.
@param pq A pointer to a priority queue.
Expand Down
Loading