/* * pqueue - A priority-queue extension for Python using Fibonacci heaps. * Copyright (C) 1999 Andrew Snare * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ /* Note to developers :- * Fibonnaci heaps are nasty. Do _NOT_ attempt to debug this code * unless you really really know what you're doing and have a copy * of "Introduction to Algorithms" (Thomas Cormen et al) by your side. */ #include "pqueuemodule-config.h" #include #include #include #define MAXDEG (64) /* Maximum degree of any node */ /* NOTE: This allows us to handle ~ 2.3723E13 nodes */ #undef DEBUG /* Turns on debugging stuff */ struct heapnodeRec { struct heapnodeRec *p; /* Pointer to the parent */ struct heapnodeRec *child; /* Pointer to any of the children */ struct heapnodeRec *left; /* Pointer to the left sibling */ struct heapnodeRec *right; /* Pointer to the right sibling */ int degree; /* How many children we have */ int mark; /* Lost child since last made child */ PyObject *priority; /* Priority associated with node */ PyObject *data; /* Data associated with the node */ }; typedef struct heapnodeRec heapnode; struct heapnodetrampRec { heapnode *ptr; /* Trampoline pointer */ int refcount; /* Reference counter */ }; typedef struct heapnodetrampRec heapnodetramp; typedef struct { PyObject_HEAD heapnode *min; /* Pointer to current minimum */ int n; /* Number of nodes in the pq */ PyObject *dict; /* Dictionary of data */ } pqueueobject; staticforward PyTypeObject PQueuetype; /* PQueue -- some debugging routines */ #ifdef DEBUG #define LONG(x) (PyInt_AS_LONG((x)->priority)) static void display_children(child, level, parent) heapnode *child; int level; heapnode *parent; { /* This is a debugging routine. It displays a child list recursively */ if (child) { heapnode *w = child; do { PyObject* priority = PyObject_Repr(w->priority); PyObject* data = PyObject_Repr(w->data); printf("%*s%#x P(%#x) L(%#x) R(%#x) D(%d) M(%d) " "(%s, %s) %#x", level*4, "", w, w->p, w->left, w->right, w->degree, w->mark, PyString_AS_STRING(priority), PyString_AS_STRING(data), w->child); Py_DECREF(priority); Py_DECREF(data); if (w->child ) { printf(" -> \n"); display_children(w->child, level+1, w); } else printf("\n"); #ifdef DEBUG assert( w->p == parent ); #endif /* DEBUG */ w = w->right; } while (child != w); } } static void display_pqueue(self) pqueueobject *self; { /* This is a debugging routine. Attempt to display the heap as best as we can. */ printf("PQueue at %#x with %d nodes.\n", self, self->n); if (self->min != NULL) { heapnode *x; printf("Min -> %#x\n", self->min); display_children(self->min, 1, NULL); } } static int check_children(child,level) heapnode *child; int level; { heapnode *w = child; heapnode *p = child->p; int count = 0; int degree = 0; do { int result; printf("%*sNow checking: %#x-[%ld]\n", level*4, "", w,LONG(w)); /* Check the parent link is correct */ if (p != w->p) printf("%*s%#x-[%ld]'s parent-link not intact -> %#x-[%ld]\n", level*4, "", w, LONG(w), w->p, LONG(w->p)); /* Check the left link */ if (w->left->right != w) printf("%*s-L-> %#x-[%ld] -R-> %#x-[%ld]\n", level*4,"", w->left, LONG(w->left), w->left->right, LONG(w->left->right)); /* Check the right link */ if (w->right->left != w) printf("%*s-R-> %#x-[%ld] -L-> %#x-[%ld]\n", level*4,"", w->right, LONG(w->right), w->right->left, LONG(w->left->right)); /* Check the heap condition */ if (w->p != NULL) { PyObject_Cmp(w->priority, w->p->priority, &result); if (result < 0) printf("%*sHeap-condition violated: %#x-[%ld] > %#x-[%ld]\n", level*4,"", w->p,LONG(w->p), w,LONG(w)); } /* Recur to children if they're present */ if (w->child) { printf("%*s(should have %d children -> %#x-[%ld])\n", level*4, "", w->degree,w->child,LONG(w->child)); if( w->child->p != w ) printf("%*s(doesn't point back -> %#x-[%ld])\n", level*4,"",w->child->p,LONG(w->child->p)); count += check_children(w->child, level+1); } degree++; w = w->right; } while (w != child); /* Assert the degree was correct */ if( w->p == NULL ) printf("%*s(no parent, degree information unchecked)\n", level*4, ""); else if (degree != w->p->degree) printf("%*s(%d children, %d expected from parent)\n", level*4, "", degree, w->p->degree); count += degree; return count; } static void check_heap(pqp) pqueueobject *pqp; { if (pqp->min != NULL) { int count = check_children(pqp->min,0); if (count == pqp->n) printf("Hmm... %d nodes expected and accounted for.\n", pqp->n); else printf("Doh! %d nodes expected, only %d found.\n", pqp->n, count); } } #endif /* DEBUG */ /* PQueue -- C API -- Constructor */ #define is_pqueueobject(v) ((v)->ob_type == &PQueuetype) static pqueueobject * pqueue_new() { pqueueobject *pqp; pqp = PyObject_NEW(pqueueobject, &PQueuetype); if (pqp == NULL) return NULL; /* Allocate the dictionary */ pqp->dict = PyDict_New(); if (pqp->dict == NULL) return NULL; /* No minimum to start off with (and also no nodes) */ pqp->min = NULL; pqp->n = 0; return pqp; } /* PQueue methods */ static void children_dealloc(child) heapnode *child; { child->left->right = NULL; do { heapnode *x = child; if (x->child != NULL) { x->child->left->right = x->right; x->right = x->child; } Py_DECREF(x->priority); Py_DECREF(x->data); child = child->right; free(x); } while( child != NULL ); } static void pqueue_dealloc(pqp) pqueueobject *pqp; { Py_DECREF(pqp->dict); if(pqp->min != NULL) children_dealloc(pqp->min); PyMem_DEL(pqp); } static PyObject * pqueue_insert(self, args) pqueueobject *self; PyObject *args; { PyObject *priority, *data, *ptr; heapnode *x; heapnodetramp *tramp; int newmin, newdata; /* Check the parameters first */ if (!PyArg_ParseTuple(args, "OO:insert", &priority, &data)) return NULL; /* Retrieve the data if it already exists */ ptr = PyDict_GetItem(self->dict, data); if ((ptr == NULL) && (PyErr_Occurred() != NULL)) return NULL; /* Do the comparison early on to detect errors early */ Py_INCREF(priority); Py_INCREF(data); if (self->min != NULL) { if(PyObject_Cmp(self->min->priority, priority, &newmin) == -1){ PyErr_SetString(PyExc_ValueError, "unable to compare priority"); Py_DECREF(priority); Py_DECREF(data); return NULL; } } /* Then try and allocate the node */ if (NULL == (x = malloc(sizeof(heapnode)))) { PyErr_NoMemory(); Py_DECREF(priority); Py_DECREF(data); return NULL; } /* Now make the CObject and put it in the dictionary (if need be) */ if (ptr == NULL) { PyObject *cobject; tramp = malloc(sizeof(heapnodetramp)); cobject = PyCObject_FromVoidPtr(tramp, free); if ((tramp == NULL) || (cobject == NULL) || (PyDict_SetItem(self->dict, data, cobject) == -1)) { /* If things failed, clean up and go home */ Py_XDECREF(cobject); Py_DECREF(priority); Py_DECREF(data); free(x); if (tramp != NULL) free(tramp); return NULL; } Py_DECREF(cobject); /* Since PyDict_SetItem borrows */ tramp->ptr = x; tramp->refcount = 1; } else { tramp = (heapnodetramp*)PyCObject_AsVoidPtr(ptr); /* CObject already exists, increment the counter */ tramp->ptr = NULL; tramp->refcount++; } /* Initialize the node structure */ x->degree = 0; x->p = NULL; x->child = NULL; x->mark = 0; x->priority = priority; x->data = data; /* Insert the node into the root list */ if (self->min == NULL) { self->min = x->left = x->right = x; } else { x->right = self->min; x->left = self->min->left; self->min->left->right = x; self->min->left = x; if (newmin > 0) { self->min = x; } } self->n++; /* We return None to indicate success */ Py_INCREF(Py_None); return Py_None; } static PyObject * pqueue_peek(self, args) pqueueobject *self; PyObject *args; { /* Check the parameters first */ if (!PyArg_ParseTuple(args, ":peek")) return NULL; /* If empty, return an error */ if (self->min == NULL) { PyErr_SetString(PyExc_IndexError, "nothing in the queue"); return NULL; } /* Return a tuple of the current min node */ return Py_BuildValue("OO", self->min->priority, self->min->data); } static inline void consolidate(self) pqueueobject *self; { #ifdef DEBUG printf("Starting consolidate()\n"); #endif /* DEBUG */ if (self->min != NULL) { int i; heapnode *A[MAXDEG]; memset(A, 0, sizeof(heapnode*)*MAXDEG); /* We break the link to detect when we've gone through all the nodes. This can be done since we only remove nodes and never insert them. */ self->min->left->right = NULL; do { heapnode *x = self->min; int d = x->degree; /* Advance to the next one while we can */ self->min = self->min->right; #ifdef DEBUG printf("Looking at %#x-[%ld] (d=%d).\n", x, LONG(x), d); #endif /* DEBUG */ while( A[d] != NULL ) { int cmpresult; heapnode *y = A[d]; #ifdef DEBUG printf("Doh! %#x-[%ld] already has d=%d.\n", y, LONG(y), d); #endif /* DEBUG */ /* This _should_ never fail? */ PyObject_Cmp(x->priority, y->priority, &cmpresult); if (cmpresult > 0) { heapnode *t = x; x = y; y = t; #ifdef DEBUG printf("(and we're bigger)\n"); #endif /* DEBUG */ } /* Make y a child of x */ #ifdef DEBUG printf("Making %#x-[%ld] a child of %#x-[%ld].\n", y, LONG(y), x, LONG(x)); #endif /* DEBUG */ y->p = x; if (x->child == NULL) x->child = y->left = y->right = y; else { y->right = x->child; y->left = x->child->left; x->child->left->right = y; x->child->left = y; } x->degree++; /* Mark y as having been made a child */ y->mark = 0; A[d++] = NULL; } A[d] = x; #ifdef DEBUG printf("Storing %#x-[%ld] at d=%d.\n", x, LONG(x), d); #endif /* DEBUG */ } while (self->min != NULL); #ifdef DEBUG printf("Time to build the root list....\n"); #endif /* DEBUG */ for(i=0; imin == NULL) { self->min = A[i]->left = A[i]->right = A[i]; } else { int newmin; A[i]->right = self->min; A[i]->left = self->min->left; self->min->left->right = A[i]; self->min->left = A[i]; /* Check to see if we have a new min */ PyObject_Cmp(self->min->priority, A[i]->priority, &newmin); if (newmin > 0) { self->min = A[i]; } } } } } static PyObject * pqueue_pop(self, args) pqueueobject *self; PyObject *args; { PyObject *ret; heapnode *min; heapnode *child; heapnodetramp *tramp; /* Check the parameters first */ if (!PyArg_ParseTuple(args, ":pop")) return NULL; /* Enter the debugger... */ /* raise(SIGTRAP); */ #ifdef DEBUG check_heap(self); #endif /* DEBUG */ min = self->min; /* If empty, return an error */ if (min == NULL) { PyErr_SetString(PyExc_IndexError, "nothing in the queue"); return NULL; } #ifdef DEBUG printf("Removing min from the root list...\n"); #endif /* DEBUG */ /* Remove the children of the min-node and put them on root list */ child = min->child; if (child != NULL) { heapnode *c = child; /* Set each child to have no parent */ do { c->p = NULL; c = c->right; } while(c != child); /* Now put the child-list on the root list */ min->left->right = child; child->left->right = min; c = child->left; child->left = min->left; min->left = c; } /* Now pull the min-node out of the root list */ min->left->right = min->right; min->right->left = min->left; /* Check if we're the only node in the root list */ if (min == min->right) self->min = NULL; else { self->min = min->right; #ifdef DEBUG check_heap(self); #endif /* DEBUG */ consolidate(self); } self->n--; /* Reduce the refcount for the data */ tramp = PyCObject_AsVoidPtr(PyDict_GetItem(self->dict, min->data)); if (--(tramp->refcount) == 0) { PyDict_DelItem(self->dict, min->data); } /* Build the return tuple, and de-allocate the node */ ret = Py_BuildValue("OO", min->priority, min->data); Py_DECREF(min->priority); Py_DECREF(min->data); free(min); return ret; } #ifdef DEBUG static PyObject * pqueue_display(self, args) pqueueobject *self; PyObject *args; { /* Check the parameters first */ if (!PyArg_ParseTuple(args, ":display")) return NULL; display_pqueue(self); Py_INCREF(Py_None); return Py_None; } #endif /* DEBUG */ static PyMethodDef pqueue_methods[] = { {"insert", (PyCFunction)pqueue_insert, METH_VARARGS}, {"peek", (PyCFunction)pqueue_peek, METH_VARARGS}, {"pop", (PyCFunction)pqueue_pop, METH_VARARGS}, #ifdef DEBUG {"display", (PyCFunction)pqueue_display, METH_VARARGS}, #endif /* DEBUG */ {NULL, NULL} /* Sentinel */ }; static int pqueue_length(pqp) pqueueobject *pqp; { return pqp->n; } static PyObject * pqueue_subscript(pqp, key) pqueueobject *pqp; PyObject *key; { heapnode *hp; heapnodetramp *tramp; PyObject *cobject = PyDict_GetItem(pqp->dict, key); if ((cobject == NULL) || ((tramp = PyCObject_AsVoidPtr(cobject))->ptr == NULL)) { PyErr_SetObject(PyExc_KeyError, key); return NULL; } hp = tramp->ptr; Py_INCREF(hp->priority); return hp->priority; } static inline void cut(pqp, x, y) pqueueobject *pqp; heapnode *x, *y; { #ifdef DEBUG printf("Starting cut()\n"); #endif /* DEBUG */ /* Remove x from the child list of y */ if (x->right == x) /* Only child */ y->child = NULL; else { if (y->child == x) /* Is it worth doing this test? */ y->child = x->right; x->right->left = x->left; x->left->right = x->right; } y->degree--; /* Put x on the root list */ x->left = pqp->min->left; x->right = pqp->min; pqp->min->left->right = x; pqp->min->left = x; x->p = NULL; x->mark = 0; } static void cascading_cut(pqp, y) pqueueobject *pqp; heapnode *y; { heapnode *z = y->p; #ifdef DEBUG printf("Starting cascading_cut()\n"); #endif /* DEBUG */ if (z != NULL) { if (y->mark == 0) y->mark = 1; else { cut(pqp, y, z); cascading_cut(pqp, z); } } } static int decrease_key(pqp, x, priority) pqueueobject *pqp; heapnode *x; PyObject *priority; { /* Assume we've already checked that x->priority <= priority */ int result = -1; heapnode *y = x->p; #ifdef DEBUG printf("Starting decrease_key()\n"); #endif /* DEBUG */ if (y != NULL) { if ((priority != NULL) && (PyObject_Cmp(priority, y->priority, &result) == -1)) { Py_DECREF(priority); PyErr_SetString(PyExc_ValueError, "unable to compare value"); return -1; } } /* Throw away the old priority now */ Py_DECREF(x->priority); x->priority = priority; /* If we need to move the node up the tree, do it */ if ((y != NULL) && (result < 0)) { cut(pqp,x,y); cascading_cut(pqp,y); } /* If we have a new minimum, make note of it */ if (priority != NULL) PyObject_Cmp(x->priority, pqp->min->priority, &result); if (result < 0) pqp->min = x; return 0; } static inline int delete_key(pqp, x) pqueueobject *pqp; heapnode *x; { PyObject *min; /* Now do a reduce on it */ decrease_key(pqp, x, NULL); /* NULL == -infinity */ /* Now put in the Py_None for the priority */ Py_INCREF(Py_None); x->priority = Py_None; /* Now do the extract-min to remove the same element */ min = pqueue_pop(pqp, PyTuple_New(0)); if (min == NULL) return -1; /* Throw away the result */ Py_DECREF(min); return 0; } static int pqueue_ass_sub(pqp, data, priority) pqueueobject *pqp; PyObject *data, *priority; { int result; heapnode *hp; heapnodetramp *tramp; PyObject *cobject = PyDict_GetItem(pqp->dict, data); /* Check we could find the node they're referring to */ if ((cobject == NULL) || ((tramp = PyCObject_AsVoidPtr(cobject))->ptr == NULL)) { if (priority == NULL) { /* Deleting non-existent node */ PyErr_SetObject(PyExc_KeyError, data); return -1; } else { /* Setting non-existent node */ /* Turn the set into an insert() */ PyObject *ret = pqueue_insert(pqp, Py_BuildValue("OO", priority, data)); if (ret == NULL) return -1; Py_DECREF(ret); return 0; } } /* Find the node they're talking about */ hp = tramp->ptr; /* Check if we're doing a deletion */ if (priority == NULL) { return delete_key(pqp, hp); } /* Next check they're reducing the key */ if (PyObject_Cmp(priority, hp->priority, &result) == -1) { PyErr_SetString(PyExc_ValueError, "unable to compare value"); return -1; } else if (result > 0) { /* New key is greater than old. Do a delete/insert. */ int ret = delete_key(pqp, hp); if (ret != 0) return ret; else { PyObject *ret = pqueue_insert(pqp, Py_BuildValue("OO", priority, data)); if (ret == NULL) return -1; Py_DECREF(ret); return 0; } } #ifdef DEBUG check_heap(pqp); #endif /* DEBUG */ /* Take ownership of the new priority, and assign it to the node */ Py_INCREF(priority); return decrease_key(pqp, hp, priority); } static PyMappingMethods pqueue_as_mapping = { (inquiry)pqueue_length, /* mp_length */ (binaryfunc)pqueue_subscript, /* mp_subscript */ (objobjargproc)pqueue_ass_sub, /* mp_ass_subscript */ }; static PyObject * pqueue_getattr(pqp, name) pqueueobject *pqp; char *name; { return Py_FindMethod(pqueue_methods, (PyObject *)pqp, name); } static int pqueue_setattr(pqp, name, v) pqueueobject *pqp; char *name; PyObject *v; { PyErr_SetString(PyExc_AttributeError, "can't modify pqueue attributes"); return -1; } static char PQueuetype_Type__doc__[] = "Priority queues are used as a FIFO buffer, but with the difference that\n\ items in the queue have a priority associated with them. The item with the\n\ lowest priority is always extracted from the list first.\n"; static PyTypeObject PQueuetype = { PyObject_HEAD_INIT(&PyType_Type) 0, /* ob_size */ "pqueue", /* tp_name */ sizeof(pqueueobject), /* tp_basicsize */ 0, /* tp_itemsize */ /* methods */ (destructor)pqueue_dealloc, /* tp_dealloc */ 0, /* tp_print */ (getattrfunc)pqueue_getattr, /* tp_getattr */ (setattrfunc)pqueue_setattr, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ &pqueue_as_mapping, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0L,0L,0L,0L, /* future expansion */ PQueuetype_Type__doc__ /* Documentation string */ }; /* PQueue -- Python API -- Constructor */ static PyObject * pqueue_PQueue(self, args) PyObject *self; PyObject *args; { if (!PyArg_ParseTuple(args, "")) return NULL; return (PyObject *)pqueue_new(); } static PyMethodDef PQueueMethods[] = { {"PQueue", (PyCFunction)pqueue_PQueue, METH_VARARGS}, {NULL, NULL} /* Sentinel */ }; void initpqueue() { (void) Py_InitModule("pqueue", PQueueMethods); }