Import priority queue implementation from Cehteh's library

This commit is contained in:
Fischlurch 2013-09-13 03:28:50 +02:00
commit 87a84a931f
2 changed files with 537 additions and 0 deletions

390
src/lib/priqueue.c Normal file
View file

@ -0,0 +1,390 @@
/*
priqueue - priority queue
Copyright (C)
2011 Christian Thaeter <ct@pipapo.org>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "priqueue.h"
#include <nobug.h>
#include <stdint.h>
NOBUG_DEFINE_FLAG (priqueue);
PriQueue
priqueue_init (PriQueue self,
size_t element_size,
priqueue_cmp_fn cmpfn,
priqueue_copy_fn copyfn,
priqueue_resize_fn resizefn)
{
NOBUG_INIT_FLAG (priqueue);
TRACE (priqueue, "%p", self);
REQUIRE (element_size);
REQUIRE (cmpfn);
if (self)
{
self->queue = NULL;
self->element_size = element_size;
self->used = self->high_water = self->low_water = 0;
self->cmpfn = cmpfn;
if (!copyfn)
copyfn = memcpy;
self->copyfn = copyfn;
if (!resizefn)
resizefn = priqueue_clib_resize;
self->resizefn = resizefn;
self = self->resizefn (self);
}
return self;
}
PriQueue
priqueue_destroy (PriQueue self)
{
TRACE (priqueue, "%p", self);
if (self)
{
WARN_IF (self->used, priqueue, "queue was not empty");
self->used = 0;
self = self->resizefn (self);
}
return self;
}
PriQueue
priqueue_reserve (PriQueue self, unsigned elements)
{
TRACE (priqueue, "%p %d", self, elements);
if (self)
{
if (self->used+elements >= self->high_water)
{
self->used += elements;
self = self->resizefn (self);
if (!self)
{
ERROR (priqueue, "resize failed");
return NULL;
}
self->used -= elements;
}
self->low_water = 0;
}
return self;
}
/*
supplied/default resize function based on realloc
*/
PriQueue
priqueue_clib_resize (PriQueue self)
{
if (self)
{
if (!self->queue)
{
INFO (priqueue, "%p: initial alloc", self);
self->queue = malloc (64*self->element_size);
ERROR_IF (!self->queue, priqueue, "%p: allocation failed", self);
if (!self->queue)
return NULL;
self->high_water = 64;
}
else
{
if (self->used)
{
if (self->used >= self->high_water)
{
unsigned newwater = self->high_water;
while (self->used >= newwater)
newwater *= 2;
INFO (priqueue, "%p: resize %d -> %d", self, self->high_water, newwater);
void* newqueue = realloc (self->queue, self->element_size * newwater);
ERROR_IF (!newqueue, priqueue, "%p: allocation failed", self);
if (!newqueue)
return NULL;
self->queue = newqueue;
self->high_water = newwater;
self->low_water = self->high_water/8-8;
TRACE (priqueue, "%p: low_water: %d", self, self->low_water);
}
else
{
INFO (priqueue, "%p: shrink %d -> %d", self, self->high_water, (self->low_water+8)*4);
void* newqueue = realloc (self->queue, self->element_size * (self->low_water+8)*4);
ERROR_IF (!newqueue, priqueue, "allocation failed");
if (!newqueue)
return NULL;
self->queue = newqueue;
self->high_water = (self->low_water+8)*4;
self->low_water = self->high_water/8-8;
TRACE (priqueue, "%p: low_water: %d", self, self->low_water);
}
}
else
{
INFO (priqueue, "%p: freeing", self);
free (self->queue);
self->queue = NULL;
}
}
}
return self;
}
static inline void*
pq_index (PriQueue self, unsigned nth)
{
return (char*)self->queue+self->element_size*nth;
}
static inline void
pq_up (PriQueue self, void* tmp)
{
unsigned i = self->used;
unsigned p = i/2;
while (p && self->cmpfn (tmp, pq_index(self, p-1)) < 0)
{
self->copyfn (pq_index (self, i-1), pq_index (self, p-1), self->element_size);
i=p; p=i/2;
}
self->copyfn (pq_index (self, i-1), tmp, self->element_size);
}
PriQueue
priqueue_insert (PriQueue self, void* element)
{
TRACE (priqueue, "%p: insert %p", self, element);
if (self && self->used >= self->high_water)
self = self->resizefn (self);
if (self)
{
++self->used;
pq_up (self, element);
}
return self;
}
static inline void
pq_down (PriQueue self, void* tmp)
{
if (!self->used)
return;
unsigned i = 1;
while (i <= self->used/2)
{
unsigned n=i+i;
if (n<self->used && self->cmpfn (pq_index(self, n-1), pq_index(self, n)) >= 0)
++n;
if (self->cmpfn (tmp, pq_index(self, n-1)) < 0)
break;
self->copyfn (pq_index (self, i-1), pq_index (self, n-1), self->element_size);
i = n;
}
self->copyfn (pq_index (self, i-1), tmp, self->element_size);
}
PriQueue
priqueue_remove (PriQueue self)
{
TRACE (priqueue, "%p: remove", self);
if (self)
{
if (!self->used)
return NULL;
--self->used;
pq_down (self, pq_index (self, self->used));
if (self->used < self->low_water)
self = self->resizefn (self);
}
return self;
}
#ifdef PRIQUEUE_TEST /* testing */
#include <stdio.h>
void
nobug_priqueue_invariant (PriQueue self, int depth, const struct nobug_context invariant_context, void* extra)
{
intptr_t n = 1+(intptr_t)extra;
intptr_t m=n+n;
if (self && depth && m <= self->used)
{
INVARIANT_ASSERT (self->cmpfn (pq_index(self, n-1), pq_index(self, m-1)) <= 0, "%d %d", (int)n-1, (int)m-2);
nobug_priqueue_invariant (self, depth-1, invariant_context, (void*)m-1);
if (m<self->used)
{
INVARIANT_ASSERT (self->cmpfn (pq_index(self, n-1), pq_index(self, m)) <= 0, "%d %d", (int)n-1, (int)m-1);
nobug_priqueue_invariant (self, depth-1, invariant_context, (void*)m);
}
}
}
static int
cmpintptr (void* a, void* b)
{
return *(int*)a - *(int*)b;
}
NOBUG_DEFINE_FLAG (priqueue_test);
int main()
{
NOBUG_INIT;
NOBUG_INIT_FLAG (priqueue_test);
priqueue pq;
PriQueue r;
int data;
r = priqueue_init (&pq,
sizeof (int),
cmpintptr,
NULL,
NULL);
ENSURE (r==&pq);
#if 1
data = 10;
r = priqueue_insert (&pq, &data);
ENSURE (r==&pq);
TRACE (priqueue_test, "inserted %d", data);
#endif
#if 0
data = 5;
r = priqueue_insert (&pq, &data);
ENSURE (r==&pq);
TRACE (priqueue_test, "inserted %d", data);
#endif
#if 0
data = 15;
r = priqueue_insert (&pq, &data);
ENSURE (r==&pq);
TRACE (priqueue_test, "inserted %d", data);
data = 20;
r = priqueue_insert (&pq, &data);
ENSURE (r==&pq);
TRACE (priqueue_test, "inserted %d", data);
#endif
#if 1
for (int i = 0; i < 1000000; ++i)
{
data = i;
r = priqueue_insert (&pq, &data);
ENSURE (r==&pq);
TRACE (priqueue_test, "inserted %d", data);
}
#endif
#if 1
for (int i = 0; i < 1000000; ++i)
{
data = rand()%1000000;
r = priqueue_insert (&pq, &data);
ENSURE (r==&pq);
TRACE (priqueue_test, "inserted %d", data);
}
#endif
NOBUG_INVARIANT(priqueue, &pq, 100, NULL);
#if 1
for (int i = 0; pq.used; ++i)
{
TRACE (priqueue_test, "TOP: %d", *(int*)priqueue_peek (&pq));
r = priqueue_remove (&pq);
ENSURE (r==&pq);
}
#endif
r = priqueue_destroy (&pq);
ENSURE (r==&pq);
return 0;
}
#endif

147
src/lib/priqueue.h Normal file
View file

@ -0,0 +1,147 @@
/*
priqueue - priority queue
Copyright (C)
2011 Christian Thaeter <ct@pipapo.org>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef PRIQUEUE_H
#define PRIQUEUE_H
/*
This is a very minimalistic priority queue implementation based on a binary heap. Only 'insert' 'remove' and 'peek' operations are supported
Memory is dynamically managed through an optionally user supplied 'resize' function.
Elements in the queue have a user-define size but should kept as small as possible. This is only intended to associate lightweight data such as
a key and a pointer, storing the key in the element can save dereferencing cost and thus improve cache locality. It must be noted
that elements in the queue get moved in memory, so referencing them is invalid.
There are API's (yet) to change the priority of an arbitary element or remove any but the topmost element. The idea is to let expired elements
sink to the top and detect that and then remove them.
*/
#include <stdlib.h>
typedef struct priqueue_struct priqueue;
typedef priqueue* PriQueue;
/* function to compare 2 keys, mandatory */
typedef int (*priqueue_cmp_fn)(void*, void*);
/* function to copy elements, optional. Has the same prototype as memcpy which is used by default */
typedef void *(*priqueue_copy_fn)(void *dest, const void *src, size_t n);
/* called when used hits the high or low water marks and initially by priqueue_init() (with queue==NULL)
or at priqueue_destroy (with queue != NULL, used elements == 0),
optional.
Must be aware of resizes by more than just incrementing the queue by one
*/
typedef PriQueue (*priqueue_resize_fn) (PriQueue);
/*
this structure is not opaque to make it possible to implement a low level resize operation which has to reallocate
the queue and update the high and low water marks.
*/
struct priqueue_struct
{
void* queue;
size_t element_size;
unsigned used;
unsigned high_water; // elements in the queue
unsigned low_water; // size for shrinking the queue
priqueue_cmp_fn cmpfn;
priqueue_copy_fn copyfn;
priqueue_resize_fn resizefn;
};
PriQueue
priqueue_init (PriQueue self,
size_t element_size,
priqueue_cmp_fn cmpfn,
priqueue_copy_fn copyfn,
priqueue_resize_fn resizefn);
PriQueue
priqueue_destroy (PriQueue self);
/*
calls resize to match for at least 'elements' in the queue and then sets low_water to 0, disabling shrinking
note that on overflow resize will re-enable low_water if it is not aware of this
*/
PriQueue
priqueue_reserve (PriQueue self, unsigned elements);
/*
supplied/default resize function based on realloc
initially allocates an array for 64 elements,
doubles this when the high water mark is hit,
shrinks at high_water/8-8 (that is, 64 is the minimum size)
*/
PriQueue
priqueue_clib_resize (PriQueue self);
/*
insert a new element into the priority queue
the element will be copied
returns NULL on error
*/
PriQueue
priqueue_insert (PriQueue self, void* element);
/*
returns a pointer to the topmost element
note that this pointer is only valid as long no insert or remove is called
returns NULL when the queue is empty
*/
static inline void*
priqueue_peek (PriQueue self)
{
if (self && self->queue)
return self->queue;
return NULL;
}
/*
removes the topmost element
returns NULL on error (queue emtpy, resize failure)
*/
PriQueue
priqueue_remove (PriQueue self);
#endif