diff options
Diffstat (limited to 'src/lib/heap.c')
-rw-r--r-- | src/lib/heap.c | 356 |
1 files changed, 356 insertions, 0 deletions
diff --git a/src/lib/heap.c b/src/lib/heap.c new file mode 100644 index 0000000..a2e8f78 --- /dev/null +++ b/src/lib/heap.c @@ -0,0 +1,356 @@ +RCSID("$Id$") + +#include <freeradius-devel/libradius.h> +#include <freeradius-devel/heap.h> + +/* + * A heap entry is made of a pointer to the object, which + * contains the key. The heap itself is an array of pointers. + * + * Heaps normally support only ordered insert, and extraction + * of the minimum element. The heap entry can contain an "int" + * field that holds the entries position in the heap. The offset + * of the field is held inside of the heap structure. + */ + +struct fr_heap_t { + int size; + int num_elements; + size_t offset; + fr_heap_cmp_t cmp; + void **p; +}; + +/* + * First node in a heap is element 0. Children of i are 2i+1 and + * 2i+2. These macros wrap the logic, so the code is more + * descriptive. + */ +#define HEAP_PARENT(x) ( ( (x) - 1 ) / 2 ) +#define HEAP_LEFT(x) ( 2*(x) + 1 ) +/* #define HEAP_RIGHT(x) ( 2*(x) + 2 ) */ +#define HEAP_SWAP(a, b) { void *_tmp = a; a = b; b = _tmp; } + +static int fr_heap_bubble(fr_heap_t *hp, int child); + +void fr_heap_delete(fr_heap_t *hp) +{ + if (!hp) return; + + free(hp->p); + free(hp); +} + +fr_heap_t *fr_heap_create(fr_heap_cmp_t cmp, size_t offset) +{ + fr_heap_t *fh; + + if (!cmp) return NULL; + + fh = malloc(sizeof(*fh)); + if (!fh) return NULL; + + memset(fh, 0, sizeof(*fh)); + + fh->size = 2048; + fh->p = malloc(sizeof(*(fh->p)) * fh->size); + if (!fh->p) { + free(fh); + return NULL; + } + + fh->cmp = cmp; + fh->offset = offset; + + return fh; +} + +/* + * Insert element in heap. Normally, p != NULL, we insert p in a + * new position and bubble up. If p == NULL, then the element is + * already in place, and key is the position where to start the + * bubble-up. + * + * Returns 1 on failure (cannot allocate new heap entry) + * + * If offset > 0 the position (index, int) of the element in the + * heap is also stored in the element itself at the given offset + * in bytes. + */ +#define SET_OFFSET(heap, node) \ + if (heap->offset) \ + *((int *)(((uint8_t *)heap->p[node]) + heap->offset)) = node + +/* + * RESET_OFFSET is used for sanity checks. It sets offset to an + * invalid value. + */ +#define RESET_OFFSET(heap, node) \ + if (heap->offset) \ + *((int *)(((uint8_t *)heap->p[node]) + heap->offset)) = -1 + +int fr_heap_insert(fr_heap_t *hp, void *data) +{ + int child = hp->num_elements; + + /* + * Heap is full. Double it's size. + */ + if (child == hp->size) { + void **p; + + p = malloc(2 * hp->size * sizeof(*p)); + if (!p) return 0; + + memcpy(p, hp->p, sizeof(*p) * hp->size); + free(hp->p); + hp->p = p; + hp->size *= 2; + } + + hp->p[child] = data; + hp->num_elements++; + + return fr_heap_bubble(hp, child); +} + + +static int fr_heap_bubble(fr_heap_t *hp, int child) +{ + /* + * Bubble up the element. + */ + while (child > 0) { + int parent = HEAP_PARENT(child); + + /* + * Parent is smaller than the child. We're done. + */ + if (hp->cmp(hp->p[parent], hp->p[child]) < 0) break; + + /* + * Child is smaller than the parent, repeat. + */ + HEAP_SWAP(hp->p[child], hp->p[parent]); + SET_OFFSET(hp, child); + child = parent; + } + SET_OFFSET(hp, child); + + return 1; +} + + +/* + * Remove the top element, or object. + */ +int fr_heap_extract(fr_heap_t *hp, void *data) +{ + int child, parent; + int max; + + if (!hp || (hp->num_elements == 0)) return 0; + + max = hp->num_elements - 1; + + /* + * Extract element. Default is the first one. + */ + if (!data) { + parent = 0; + + } else { /* extract from the middle */ + if (!hp->offset) return 0; + + parent = *((int *)(((uint8_t *)data) + hp->offset)); + + /* + * Out of bounds. + */ + if (parent < 0 || parent >= hp->num_elements) return 0; + } + + RESET_OFFSET(hp, parent); + child = HEAP_LEFT(parent); + while (child <= max) { + /* + * Maybe take the right child. + */ + if ((child != max) && + (hp->cmp(hp->p[child + 1], hp->p[child]) < 0)) { + child = child + 1; + } + hp->p[parent] = hp->p[child]; + SET_OFFSET(hp, parent); + parent = child; + child = HEAP_LEFT(child); + } + hp->num_elements--; + + /* + * We didn't end up at the last element in the heap. + * This element has to be re-inserted. + */ + if (parent != max) { + /* + * Fill hole with last entry and bubble up, + * reusing the insert code + */ + hp->p[parent] = hp->p[max]; + return fr_heap_bubble(hp, parent); + } + + return 1; +} + + +void *fr_heap_peek(fr_heap_t *hp) +{ + if (!hp || (hp->num_elements == 0)) return NULL; + + /* + * If this is NULL, we have a problem. + */ + return hp->p[0]; +} + +int fr_heap_num_elements(fr_heap_t *hp) +{ + if (!hp) return 0; + + return hp->num_elements; +} + + +#ifdef TESTING +static bool fr_heap_check(fr_heap_t *hp, void *data) +{ + int i; + + if (!hp || (hp->num_elements == 0)) return false; + + for (i = 0; i < hp->num_elements; i++) { + if (hp->p[i] == data) { + return true; + } + } + + return false; +} + +typedef struct heap_thing { + int data; + int heap; /* for the heap */ +} heap_thing; + + +/* + * cc -g -DTESTING -I .. heap.c -o heap + * + * ./heap + */ +static int heap_cmp(void const *one, void const *two) +{ + heap_thing const *a; + heap_thing const *b; + + a = (heap_thing const *) one; + b = (heap_thing const *) two; + + return a->data - b->data; + +} + +#define ARRAY_SIZE (1024) + +int main(int argc, char **argv) +{ + fr_heap_t *hp; + int i; + heap_thing array[ARRAY_SIZE]; + int skip = 0; + int left; + + if (argc > 1) { + skip = atoi(argv[1]); + } + + hp = fr_heap_create(heap_cmp, offsetof(heap_thing, heap)); + if (!hp) { + fprintf(stderr, "Failed creating heap!\n"); + fr_exit(1); + } + + for (i = 0; i < ARRAY_SIZE; i++) { + array[i].data = rand() % 65537; + if (!fr_heap_insert(hp, &array[i])) { + fprintf(stderr, "Failed inserting %d\n", i); + fr_exit(1); + } + + if (!fr_heap_check(hp, &array[i])) { + fprintf(stderr, "Inserted but not in heap %d\n", i); + fr_exit(1); + } + } + +#if 0 + for (i = 0; i < ARRAY_SIZE; i++) { + printf("Array %d has value %d at offset %d\n", + i, array[i].data, array[i].heap); + } +#endif + + if (skip) { + int entry; + + printf("%d elements to remove\n", ARRAY_SIZE / skip); + + for (i = 0; i < ARRAY_SIZE / skip; i++) { + entry = i * skip; + + if (!fr_heap_extract(hp, &array[entry])) { + fprintf(stderr, "Failed removing %d\n", entry); + } + + if (fr_heap_check(hp, &array[entry])) { + fprintf(stderr, "Deleted but still in heap %d\n", entry); + fr_exit(1); + } + + if (array[entry].heap != -1) { + fprintf(stderr, "heap offset is wrong %d\n", entry); + fr_exit(1); + } + } + } + + left = fr_heap_num_elements(hp); + printf("%d elements left in the heap\n", left); + + for (i = 0; i < left; i++) { + heap_thing *t = fr_heap_peek(hp); + + if (!t) { + fprintf(stderr, "Failed peeking %d\n", i); + fr_exit(1); + } + + printf("%d\t%d\n", i, t->data); + + if (!fr_heap_extract(hp, NULL)) { + fprintf(stderr, "Failed extracting %d\n", i); + fr_exit(1); + } + } + + if (fr_heap_num_elements(hp) > 0) { + fprintf(stderr, "%d elements left at the end", fr_heap_num_elements(hp)); + fr_exit(1); + } + + fr_heap_delete(hp); + + return 0; +} +#endif |