From 7b67376dbd9da906e42788594b01286ce1b74772 Mon Sep 17 00:00:00 2001 From: dcoudert Date: Wed, 27 Nov 2024 19:09:43 +0100 Subject: [PATCH 01/27] add a pairing heap data structure --- .../en/reference/data_structures/index.rst | 1 + src/doc/en/reference/references/index.rst | 5 + src/sage/data_structures/pairing_heap.h | 369 ++++ src/sage/data_structures/pairing_heap.pxd | 82 + src/sage/data_structures/pairing_heap.pyx | 1567 +++++++++++++++++ 5 files changed, 2024 insertions(+) create mode 100644 src/sage/data_structures/pairing_heap.h create mode 100644 src/sage/data_structures/pairing_heap.pxd create mode 100644 src/sage/data_structures/pairing_heap.pyx diff --git a/src/doc/en/reference/data_structures/index.rst b/src/doc/en/reference/data_structures/index.rst index 08c03313ad3..1832d01eb75 100644 --- a/src/doc/en/reference/data_structures/index.rst +++ b/src/doc/en/reference/data_structures/index.rst @@ -9,5 +9,6 @@ Data Structures sage/data_structures/bounded_integer_sequences sage/data_structures/stream sage/data_structures/mutable_poset + sage/data_structures/pairing_heap .. include:: ../footer.txt diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 45f5fbc090f..17accf462db 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -2762,6 +2762,11 @@ REFERENCES: Cambridge University Press, Cambridge, 2009. See also the `Errata list `_. +.. [FSST1986] Michael L. Fredman, Robert Sedgewick, Daniel D. Sleator, and + Robert E. Tarjan. *The pairing heap: A new form of self-adjusting + heap*, Algorithmica 1 (1986), 111-129. + :doi:`10.1007/BF01840439` + .. [FST2012] \A. Felikson, \M. Shapiro, and \P. Tumarkin, *Cluster Algebras of Finite Mutation Type Via Unfoldings*, Int Math Res Notices (2012) 2012 (8): 1768-1804. diff --git a/src/sage/data_structures/pairing_heap.h b/src/sage/data_structures/pairing_heap.h new file mode 100644 index 00000000000..dbcb99bc1fc --- /dev/null +++ b/src/sage/data_structures/pairing_heap.h @@ -0,0 +1,369 @@ +/* + * Pairing heap + * + * Implements a pairing heap data structure as described in [1]. See also [2] + * for more details. + * + * This implementation is templated by the type TI of items and the type TV of + * the value associated with an item. The top of the heap is the item with + * smallest value, i.e., this is a min heap data structure. The number of items + * in the heap is not fixed. It supports the following operations: + * + * - empty(): return true if the heap is empty, and false otherwise. + * + * - push(item, value): push an item to the heap with specified value. + * + * - top(): access the pair (item, value) at the top of the heap, i.e., with + * smallest value in time O(1). + * This operation assumes that the heap is not empty. + * + * - top_item(): access the item at the top of the heap in time O(1). + * This operation assumes that the heap is not empty. + * + * - top_value(): access the value of the item at the top of the heap in O(1). + * This operation assumes that the heap is not empty. + * + * - pop(): remove top item from the heap in amortize time O(log(n)) + * + * - decrease(item, new_value): change the value associated with the item to the + * specified value ``new_value`` in time o(log(n)). The new value must be + * smaller than the previous one. Otherwise the structure of the heap is no + * longer guaranteed. + * If the item is not already in the heap, this method calls method ``push``. + * + * - contains(item): check whether specified item is in the heap in time O(1). + * + * - value(item): return the value associated with the item in the heap. + * This operation assumes that the item is already in the heap. + * + * References: + * + * [1] M. L. Fredman, R. Sedgewick, D. D. Sleator, and R. E. Tarjan. + * "The pairing heap: a new form of self-adjusting heap". + * Algorithmica. 1 (1): 111-129, 1986. doi:10.1007/BF01840439. + * + * [2] https://en.wikipedia.org/wiki/Pairing_heap + * + * Author: + * - David Coudert + * + */ + +#ifndef PAIRING_HEAP_H +#define PAIRING_HEAP_H + +#include +#include +#include +#include + + +namespace pairing_heap { + + template< + typename TI, // type of items stored in the node + typename TV // type of values associated with the stored item + > + struct PairingHeapNode { + TI item; // item contained in the node + TV value; // value associated with the item + + PairingHeapNode * prev; // Previous sibling of the node or parent + PairingHeapNode * next; // Next sibling of the node + PairingHeapNode * child; // First child of the node + + explicit PairingHeapNode(const TI &some_item, const TV &some_value): + item(some_item), value(some_value), prev(nullptr), next(nullptr), child(nullptr) { + } + + bool operator<(PairingHeapNode const& other) const { + return value < other.value; + } + + bool operator<=(PairingHeapNode const& other) const { + return value <= other.value; + } + }; + + + template< + typename TI, // type of items stored in the node + typename TV // type of values associated with the stored item + > + class PairingHeap + { + public: + // Constructor + explicit PairingHeap(); + + // Copy constructor + PairingHeap(PairingHeap const *other); + + // Destructor + virtual ~PairingHeap(); + + // Return true if the heap is empty, else false + bool empty() { return root == nullptr; } + + // Return true if the heap is empty, else false + explicit operator bool() const { return root != nullptr; } + + // Insert an item into the heap with specified value (priority) + void push(const TI &some_item, const TV &some_value); + + // Return the top pair (item, value) of the heap + std::pair top(); + + // Return the top item of the heap + TI top_item(); + + // Return the top value of the heap + TV top_value(); + + // Remove the top element from the heap + void pop(); + + // Decrease the value of specified item + void decrease(const TI &some_item, const TV &new_value); + + // Check if specified item is in the heap + bool contains(TI const& some_item); + + // Return the value associated with the item + TV value(const TI &some_item); + + // Return the number of items in the heap + size_t size() { return nodes.size(); } + + private: + // Pointer to the top of the heap + PairingHeapNode *root; + + // Map used to access stored items + std::map *> nodes; + + // Pair list of heaps and return pointer to the top of resulting heap + PairingHeapNode *_pair(PairingHeapNode *p); + + // Merge 2 heaps and return pointer to the top of resulting heap + PairingHeapNode *_merge(PairingHeapNode *a, PairingHeapNode *b); + + // Make b a child of a + static void _link(PairingHeapNode *a, PairingHeapNode *b); + + // Remove p from its parent children list + static void _unlink(PairingHeapNode *p); + + }; + + // Constructor + template + PairingHeap::PairingHeap(): + root(nullptr) + { + nodes.clear(); + } + + // Copy constructor + template + PairingHeap::PairingHeap(PairingHeap const *other): + root(nullptr) + { + nodes.clear(); + for (auto const& it:other->nodes) + push(it.first, it.second->value); + } + + // Destructor + template + PairingHeap::~PairingHeap() + { + for (auto const& it: nodes) + delete it.second; + root = nullptr; + nodes.clear(); + } + + // Insert an item into the heap with specified value (priority) + template + void PairingHeap::push(const TI &some_item, const TV &some_value) + { + if (nodes.find(some_item) != nodes.end()) { + throw std::invalid_argument("item already in the heap"); + } + PairingHeapNode *p = new PairingHeapNode(some_item, some_value); + nodes[some_item] = p; + root = root == nullptr ? p : _merge(root, p); + } + + // Return the top pair (value, item) of the heap + template + inline std::pair PairingHeap::top() + { + if (root == nullptr) { + throw std::domain_error("trying to access the top of an empty heap"); + } + return std::make_pair(root->item, root->value); + } + + // Return the top item of the heap + template + inline TI PairingHeap::top_item() + { + if (root == nullptr) { + throw std::domain_error("trying to access the top of an empty heap"); + } + return root->item; + } + + // Return the top value of the heap + template + inline TV PairingHeap::top_value() + { + if (root == nullptr) { + throw std::domain_error("trying to access the top of an empty heap"); + } + return root->value; + } + + // Remove the top element from the heap + template + void PairingHeap::pop() + { + if (root != nullptr) { + PairingHeapNode *p = root->child; + nodes.erase(root->item); + delete root; + root = _pair(p); + } + } + + // Decrease the value of specified item + // If the item is not in the heap, push it + template + void PairingHeap::decrease(const TI &some_item, const TV &new_value) + { + if(contains(some_item)) { + PairingHeapNode *p = nodes[some_item]; + if (p->value <= new_value) { + throw std::invalid_argument("the new value must be less than the current value"); + } + p->value = new_value; + if(p->prev != nullptr) { + _unlink(p); + root = _merge(root, p); + } + } else { + push(some_item, new_value); + } + } + + // Check if specified item is in the heap + template + inline bool PairingHeap::contains(TI const& some_item) + { + return nodes.find(some_item) != nodes.end(); + // return nodes.count(some_item) > 0; + } + + // Return the value associated with the item + template + inline TV PairingHeap::value(const TI &some_item) + { + if (nodes.find(some_item) == nodes.end()) { + throw std::invalid_argument("the specified item is not in the heap"); + } + return nodes[some_item]->value; + } + + // Pair list of heaps and return pointer to the top of resulting heap + template + inline PairingHeapNode *PairingHeap::_pair(PairingHeapNode *p) + { + if(p == nullptr) { + return nullptr; + } + + /* + * Move toward the end of the list, counting elements along the way. + * This is done in order to: + * - know whether the list has odd or even number of nodes + * - speed up going-back through the list + */ + size_t children = 1; + PairingHeapNode *it = p; + while(it->next != nullptr) { + it = it->next; + children++; + } + + PairingHeapNode *result; + + if(children % 2 == 1) { + PairingHeapNode *a = it; + it = it->prev; + a->prev = a->next = nullptr; + result = a; + } else { + PairingHeapNode *a = it; + PairingHeapNode *b = it->prev; + it = it->prev->prev; + a->prev = a->next = b->prev = b->next = nullptr; + result = _merge(a, b); + } + + for(size_t i = 0; i < (children - 1) / 2; i++) { + PairingHeapNode *a = it; + PairingHeapNode *b = it->prev; + it = it->prev->prev; + a->prev = a->next = b->prev = b->next = nullptr; + result = _merge(_merge(a, b), result); + } + + return result; + } + + // Merge 2 heaps and return pointer to the top of resulting heap + template + inline PairingHeapNode *PairingHeap::_merge(PairingHeapNode *a, PairingHeapNode *b) + { + if(*a <= *b) { // Use comparison method of PairingHeapNode + _link(a, b); + return a; + } else { + _link(b, a); + return b; + } + } + + // Make b a child of a + template + inline void PairingHeap::_link(PairingHeapNode *a, PairingHeapNode *b) + { + if(a->child != nullptr) { + b->next = a->child; + a->child->prev = b; + } + b->prev = a; + a->child = b; + } + + // Remove p from its parent children list + template + inline void PairingHeap::_unlink(PairingHeapNode *p) + { + if(p->prev->child == p) { + p->prev->child = p->next; + } else { + p->prev->next = p->next; + } + if(p->next != nullptr) { + p->next->prev = p->prev; + } + p->prev = nullptr; + p->next = nullptr; + } + +} // end namespace pairing_heap + +#endif diff --git a/src/sage/data_structures/pairing_heap.pxd b/src/sage/data_structures/pairing_heap.pxd new file mode 100644 index 00000000000..6e8a20cc3c3 --- /dev/null +++ b/src/sage/data_structures/pairing_heap.pxd @@ -0,0 +1,82 @@ +# ****************************************************************************** +# Copyright (C) 2024 David Coudert +# +# 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. +# https://www.gnu.org/licenses/ +# ****************************************************************************** + +# ============================================================================== +# Interface to pairing heap data structure from ./pairing_heap.h +# ============================================================================== + +from libcpp.pair cimport pair + +cdef extern from "./pairing_heap.h" namespace "pairing_heap": + cdef cppclass PairingHeap[TypeOfItem, TypeOfValue]: + PairingHeap() except + + PairingHeap(PairingHeap[TypeOfItem, TypeOfValue]) except + + bint empty() + void reset() + void push(TypeOfItem, TypeOfValue) except + + pair[TypeOfItem, TypeOfValue] top() except + + TypeOfItem top_item() except + + TypeOfValue top_value() except + + void pop() except + + void decrease(TypeOfItem, TypeOfValue) except + + bint contains(TypeOfItem) + TypeOfValue value(TypeOfItem) except + + +# ============================================================================== +# Pairing heap data structure with fixed capacity n +# ============================================================================== + +from sage.data_structures.bitset_base cimport bitset_t + +ctypedef struct PairingHeapNode: + void * value # value associated with the item + PairingHeapNode * prev # Previous sibling of the node or parent + PairingHeapNode * succ # Next sibling of the node + PairingHeapNode * child # First child of the node + + +cdef bint _compare(PairingHeapNode * a, PairingHeapNode * b) except * +cdef PairingHeapNode * _pair(PairingHeapNode * p) except * +cdef PairingHeapNode * _merge(PairingHeapNode * a, PairingHeapNode * b) except * +cdef _link(PairingHeapNode * a, PairingHeapNode * b) except * +cdef _unlink(PairingHeapNode * p) except * + + +cdef class PairingHeap_class: + cdef str name # name of the data structure + cdef size_t n # maximum number of items + cdef PairingHeapNode * root # pointer to the top of the heap + cdef PairingHeapNode * nodes # array of size n to store items + cdef bitset_t active # bitset to identify active items + cdef size_t number_of_items # number of active items + cpdef bint empty(self) noexcept + cpdef bint full(self) noexcept + + +cdef class PairingHeap_of_n_integers(PairingHeap_class): + cpdef void push(self, size_t item, object value) except * + cpdef tuple top(self) except * + cpdef size_t top_item(self) except * + cpdef object top_value(self) except * + cpdef void pop(self) noexcept + cpdef void decrease(self, size_t item, object new_value) except * + cpdef object value(self, size_t item) except * + + +cdef class PairingHeap_of_n_hashables(PairingHeap_class): + cdef list _int_to_item # mapping from integers to items + cdef dict _item_to_int # mapping from items to integers + cpdef void push(self, object item, object value) except * + cpdef tuple top(self) except * + cpdef object top_item(self) except * + cpdef object top_value(self) except * + cpdef void pop(self) noexcept + cpdef void decrease(self, object item, object new_value) except * + cpdef object value(self, object item) except * diff --git a/src/sage/data_structures/pairing_heap.pyx b/src/sage/data_structures/pairing_heap.pyx new file mode 100644 index 00000000000..e320138200f --- /dev/null +++ b/src/sage/data_structures/pairing_heap.pyx @@ -0,0 +1,1567 @@ +# distutils: language = c++ +r""" +Pairing Heap + +This module proposes several implementations of the pairing heap data structure +[FSST1986]_. See the :wikipedia:`Pairing_heap` for more information on this +min-heap data structure. + +- :class:`PairingHeap_of_n_integers`: a pairing heap data structure with fixed + capacity `n`. Its items are integers in the range `0..n-1`. Values can be of + any type equipped with a comparison method (``<=``). + +- :class:`PairingHeap_of_n_hashables`: a pairing heap data structure with fixed + capacity `n`. Its items can be of any hashable type. Values can be of any type + equipped with a comparison method (``<=``). + +- ``PairingHeap``: interface to a pairing heap data structure written in C++. + The advantages of this data structure are that: its capacity is unbounded; + items can be of any hashable type; values can be of any specified type + equipped with a comparison method (``<=``). This data structure is for + internal use and therefore cannot be accessed from a shell. + +EXAMPLES: + +Pairing heap of `n` integers in the range `0..n-1`:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_integers + sage: P = PairingHeap_of_n_integers(10); P + PairingHeap_of_n_integers: capacity 10, size 0 + sage: P.push(1, 3) + sage: P.push(2, 2) + sage: P + PairingHeap_of_n_integers: capacity 10, size 2 + sage: P.top() + (2, 2) + sage: P.decrease(1, 1) + sage: P.top() + (1, 1) + sage: P.pop() + sage: P.top() + (2, 2) + + sage: P = PairingHeap_of_n_integers(10) + sage: P.push(1, (2, 'a')) + sage: P.push(2, (2, 'b')) + sage: P.top() + (1, (2, 'a')) + +Pairing heap of `n` hashables:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_hashables + sage: P = PairingHeap_of_n_hashables(10); P + PairingHeap_of_n_hashables: capacity 10, size 0 + sage: P.push(1, 3) + sage: P.push('b', 2) + sage: P.push((1, 'abc'), 4) + sage: P.top() + ('b', 2) + sage: P.decrease((1, 'abc'), 1) + sage: P.top() + ((1, 'abc'), 1) + sage: P.pop() + sage: P.top() + ('b', 2) + + sage: # needs sage.graphs + sage: P = PairingHeap_of_n_hashables(10) + sage: P.push(('a', 1), (2, 'b')) + sage: P.push(2, (2, 'a')) + sage: g = Graph(2, immutable=True) + sage: P.push(g, (3, 'z')) + sage: P.top() + (2, (2, 'a')) + sage: P.decrease(g, (1, 'z')) + sage: P.top() + (Graph on 2 vertices, (1, 'z')) + sage: while P: + ....: print(P.top()) + ....: P.pop() + (Graph on 2 vertices, (1, 'z')) + (2, (2, 'a')) + (('a', 1), (2, 'b')) + +AUTHORS: + +- David Coudert (2024) - Initial version. + + +[1] M. L. Fredman, R. Sedgewick, D. D. Sleator, and R. E. Tarjan. + "The pairing heap: a new form of self-adjusting heap". + Algorithmica. 1 (1): 111-129, 1986. doi:10.1007/BF01840439. +""" +# ****************************************************************************** +# Copyright (C) 2024 David Coudert +# +# 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. +# https://www.gnu.org/licenses/ +# ****************************************************************************** + + +from libcpp.pair cimport pair +from cpython.ref cimport PyObject, Py_INCREF, Py_XDECREF +from cysignals.signals cimport sig_on, sig_off, sig_check +from cysignals.memory cimport check_allocarray, sig_free +from sage.data_structures.bitset_base cimport (bitset_init, bitset_free, + bitset_clear, bitset_add, + bitset_remove, bitset_in, + bitset_first_in_complement) +from sage.misc.prandom import shuffle + +# ============================================================================== +# Methods for PairingHeapNode +# ============================================================================== + +cdef inline bint _compare(PairingHeapNode * a, PairingHeapNode * b) except *: + r""" + Check whether ``a.value <= b.value``. + + TESTS:: + + sage: from sage.data_structures.pairing_heap import _test_PairingHeap_of_n_integers + sage: _test_PairingHeap_of_n_integers(5) + """ + return a.value <= b.value + + +cdef inline PairingHeapNode * _pair(PairingHeapNode * p) except *: + r""" + Pair a list of heaps and return the pointer to the top of the resulting heap. + + TESTS:: + + sage: from sage.data_structures.pairing_heap import _test_PairingHeap_of_n_integers + sage: _test_PairingHeap_of_n_integers(5) + """ + if p == NULL: + return NULL + + # Move toward the end of the list, counting elements along the way. + # This is done in order to: + # - know whether the list has odd or even number of nodes + # - speed up going-back through the list + cdef size_t children = 1 + cdef PairingHeapNode * it = p + while it.succ != NULL: + it = it.succ + children += 1 + + cdef PairingHeapNode * result + cdef PairingHeapNode * a + cdef PairingHeapNode * b + + if children % 2: + a = it + it = it.prev + a.prev = a.succ = NULL + result = a + else: + a = it + b = it.prev + it = it.prev.prev + a.prev = a.succ = b.prev = b.succ = NULL + result = _merge(a, b) + + for _ in range((children - 1) // 2): + a = it + b = it.prev + it = it.prev.prev + a.prev = a.succ = b.prev = b.succ = NULL + result = _merge(_merge(a, b), result) + + return result + + +cdef inline PairingHeapNode * _merge(PairingHeapNode * a, PairingHeapNode * b) except *: + r""" + Merge two heaps and return the pointer to the top of the resulting heap. + + TESTS:: + + sage: from sage.data_structures.pairing_heap import _test_PairingHeap_of_n_integers + sage: _test_PairingHeap_of_n_integers(5) + """ + if _compare(a, b): # True if a.value <= b.value + _link(a, b) + return a + _link(b, a) + return b + + +cdef inline _link(PairingHeapNode * a, PairingHeapNode * b) except *: + r""" + Make ``b`` a child of ``a``. + + TESTS:: + + sage: from sage.data_structures.pairing_heap import _test_PairingHeap_of_n_integers + sage: _test_PairingHeap_of_n_integers(5) + """ + if a.child != NULL: + b.succ = a.child + a.child.prev = b + b.prev = a + a.child = b + + +cdef inline _unlink(PairingHeapNode * p) except *: + r""" + Remove ``p`` from the list of children of its parent. + + TESTS:: + + sage: from sage.data_structures.pairing_heap import _test_PairingHeap_of_n_integers + sage: _test_PairingHeap_of_n_integers(5) + """ + if p.prev.child == p: + p.prev.child = p.succ + else: + p.prev.succ = p.succ + if p.succ != NULL: + p.succ.prev = p.prev + p.prev = p.succ = NULL + + +# ============================================================================== +# Class PairingHeap_class +# ============================================================================== + +cdef class PairingHeap_class: + r""" + Common class and methods for :class:`PairingHeap_of_n_integers` and + :class:`PairingHeap_of_n_hashables`. + """ + def __dealloc__(self): + """ + Deallocate ``self``. + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_integers + sage: P = PairingHeap_of_n_integers(5) + sage: del P + """ + sig_free(self.nodes) + bitset_free(self.active) + + def __repr__(self): + r""" + Return a string representing ``self``. + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_integers + sage: P = PairingHeap_of_n_integers(5); P + PairingHeap_of_n_integers: capacity 5, size 0 + sage: P.push(1, 2) + sage: P + PairingHeap_of_n_integers: capacity 5, size 1 + """ + return f"{self.name}: capacity {self.n}, size {len(self)}" + + def __bool__(self): + r""" + Check whether ``self`` is not empty. + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_integers + sage: P = PairingHeap_of_n_integers(5) + sage: 'not empty' if P else 'empty' + 'empty' + sage: P.push(1, 2) + sage: 'not empty' if P else 'empty' + 'not empty' + """ + return self.root != NULL + + cpdef bint empty(self) noexcept: + r""" + Check whether the heap is empty or not. + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_integers + sage: P = PairingHeap_of_n_integers(5) + sage: P.empty() + True + sage: P.push(1, 2) + sage: P.empty() + False + """ + return self.root == NULL + + cpdef bint full(self) noexcept: + r""" + Check whether the heap is full or not. + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_integers + sage: P = PairingHeap_of_n_integers(2) + sage: P.full() + False + sage: P.push(0, 2) + sage: P.push(1, 3) + sage: P.full() + True + """ + return self.n == self.number_of_items + + def __len__(self): + r""" + Return the number of items in the heap. + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_integers + sage: P = PairingHeap_of_n_integers(5) + sage: len(P) + 0 + sage: P.push(1, 2) + sage: len(P) + 1 + """ + return self.number_of_items + + size = __len__ + + +# ============================================================================== +# Class PairingHeap_of_n_integers +# ============================================================================== + +cdef class PairingHeap_of_n_integers(PairingHeap_class): + r""" + Pairing Heap for items in range [0..n - 1]. + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_integers + sage: P = PairingHeap_of_n_integers(5); P + PairingHeap_of_n_integers: capacity 5, size 0 + sage: P.push(1, 3) + sage: P.push(2, 2) + sage: P + PairingHeap_of_n_integers: capacity 5, size 2 + sage: P.top() + (2, 2) + sage: P.decrease(1, 1) + sage: P.top() + (1, 1) + sage: P.pop() + sage: P.top() + (2, 2) + + TESTS:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_integers + sage: P = PairingHeap_of_n_integers(0) + Traceback (most recent call last): + ... + ValueError: the capacity of the heap must be strictly positive + sage: P = PairingHeap_of_n_integers(1); P + PairingHeap_of_n_integers: capacity 1, size 0 + sage: P = PairingHeap_of_n_integers(5) + sage: P.push(11, 3) + Traceback (most recent call last): + ... + ValueError: item must be in range 0..4 + """ + def __init__(self, size_t n): + r""" + Construct the ``PairingHeap_of_n_integers`` where items are integers + from ``0`` to ``n-1``. + + INPUT: + + - ``n`` -- strictly positive integer; the maximum number of items in the heap + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_integers + sage: P = PairingHeap_of_n_integers(5); P + PairingHeap_of_n_integers: capacity 5, size 0 + sage: P.push(1, 2) + sage: P + PairingHeap_of_n_integers: capacity 5, size 1 + sage: P.push(2, 3) + sage: P + PairingHeap_of_n_integers: capacity 5, size 2 + sage: P.pop() + sage: P + PairingHeap_of_n_integers: capacity 5, size 1 + sage: P.push(10, 1) + Traceback (most recent call last): + ... + ValueError: item must be in range 0..4 + sage: P = PairingHeap_of_n_integers(0) + Traceback (most recent call last): + ... + ValueError: the capacity of the heap must be strictly positive + sage: P = PairingHeap_of_n_integers(1); P + PairingHeap_of_n_integers: capacity 1, size 0 + """ + if not n: + raise ValueError("the capacity of the heap must be strictly positive") + self.name = "PairingHeap_of_n_integers" + self.n = n + self.root = NULL + self.nodes = check_allocarray(n, sizeof(PairingHeapNode)) + bitset_init(self.active, n) + bitset_clear(self.active) + self.number_of_items = 0 + + cpdef void push(self, size_t item, object value) except *: + r""" + Insert an item into the heap with specified value (priority). + + INPUT: + + - ``item`` -- non negative integer; the item to consider + + - ``value`` -- the value associated with ``item`` + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_integers + sage: P = PairingHeap_of_n_integers(5) + sage: P.push(1, 2) + sage: P.top() + (1, 2) + sage: P.push(3, 1) + sage: P.top() + (3, 1) + + TESTS:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_integers + sage: P = PairingHeap_of_n_integers(5) + sage: P.push(1, 2) + sage: P.push(1, 2) + Traceback (most recent call last): + ... + ValueError: 1 is already in the heap + sage: P.push(11, 2) + Traceback (most recent call last): + ... + ValueError: item must be in range 0..4 + """ + if item >= self.n: + raise ValueError(f"item must be in range 0..{self.n - 1}") + if item in self: + raise ValueError(f"{item} is already in the heap") + + cdef PairingHeapNode * p = self.nodes + item + Py_INCREF(value) + p.value = value + p.prev = p.succ = p.child = NULL + if self.root == NULL: + self.root = p + else: + self.root = _merge(self.root, p) + bitset_add(self.active, item) + self.number_of_items += 1 + + cpdef tuple top(self) except *: + r""" + Return the top pair (item, value) of the heap. + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_integers + sage: P = PairingHeap_of_n_integers(5) + sage: P.push(1, 2) + sage: P.top() + (1, 2) + sage: P.push(3, 1) + sage: P.top() + (3, 1) + + sage: P = PairingHeap_of_n_integers(3) + sage: P.top() + Traceback (most recent call last): + ... + ValueError: trying to access the top of an empty heap + """ + if not self: + raise ValueError("trying to access the top of an empty heap") + return self.root - self.nodes, self.root.value + + cpdef size_t top_item(self) except *: + r""" + Return the top item of the heap. + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_integers + sage: P = PairingHeap_of_n_integers(5) + sage: P.push(1, 2) + sage: P.top() + (1, 2) + sage: P.top_item() + 1 + + sage: P = PairingHeap_of_n_integers(3) + sage: P.top_item() + Traceback (most recent call last): + ... + ValueError: trying to access the top of an empty heap + """ + if not self: + raise ValueError("trying to access the top of an empty heap") + return self.root - self.nodes + + cpdef object top_value(self) except *: + r""" + Return the value of the top item of the heap. + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_integers + sage: P = PairingHeap_of_n_integers(5) + sage: P.push(1, 2) + sage: P.top() + (1, 2) + sage: P.top_value() + 2 + + sage: P = PairingHeap_of_n_integers(3) + sage: P.top_value() + Traceback (most recent call last): + ... + ValueError: trying to access the top of an empty heap + """ + if not self: + raise ValueError("trying to access the top of an empty heap") + return self.root.value + + cpdef void pop(self) noexcept: + r""" + Remove the top item from the heap. + + If the heap is already empty, we do nothing. + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_integers + sage: P = PairingHeap_of_n_integers(5); P + PairingHeap_of_n_integers: capacity 5, size 0 + sage: P.push(1, 2); P + PairingHeap_of_n_integers: capacity 5, size 1 + sage: P.push(2, 3); P + PairingHeap_of_n_integers: capacity 5, size 2 + sage: P.pop(); P + PairingHeap_of_n_integers: capacity 5, size 1 + sage: P.pop(); P + PairingHeap_of_n_integers: capacity 5, size 0 + sage: P.pop(); P + PairingHeap_of_n_integers: capacity 5, size 0 + """ + if not self: + return + cdef size_t item = self.top_item() + Py_XDECREF(self.nodes[item].value) + bitset_remove(self.active, item) + self.number_of_items -= 1 + self.root = _pair(self.root.child) + + cpdef void decrease(self, size_t item, object new_value) except *: + r""" + Decrease the value of specified item. + + This method is more permissive than it should as it can also be used to + push an item in the heap. + + INPUT: + + - ``item`` -- non negative integer; the item to consider + + - ``new_value`` -- the new value for ``item`` + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_integers + sage: P = PairingHeap_of_n_integers(5) + sage: 3 in P + False + sage: P.decrease(3, 33) + sage: 3 in P + True + sage: P.top() + (3, 33) + sage: P.push(1, 10) + sage: P.top() + (1, 10) + sage: P.decrease(3, 7) + sage: P.top() + (3, 7) + + TESTS:: + + sage: P = PairingHeap_of_n_integers(5) + sage: P.push(1, 3) + sage: P.decrease(1, 2) + sage: P.decrease(1, 2) + Traceback (most recent call last): + ... + ValueError: the new value must be less than the current value + """ + cdef PairingHeapNode * p + if bitset_in(self.active, item): + p = self.nodes + item + if p.value <= new_value: + raise ValueError("the new value must be less than the current value") + Py_XDECREF(p.value) + Py_INCREF(new_value) + p.value = new_value + if p.prev != NULL: + _unlink(p) + self.root = _merge(self.root, p) + else: + self.push(item, new_value) + + def __contains__(self, size_t item): + r""" + Check whether the specified item is in the heap. + + INPUT: + + - ``item`` -- non negative integer; the item to consider + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_integers + sage: P = PairingHeap_of_n_integers(5) + sage: 3 in P + False + sage: P.push(3, 33) + sage: 3 in P + True + sage: 100 in P + False + """ + return bitset_in(self.active, item) + + contains = __contains__ + + cpdef object value(self, size_t item) except *: + r""" + Return the value associated with the item. + + INPUT: + + - ``item`` -- non negative integer; the item to consider + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_integers + sage: P = PairingHeap_of_n_integers(5) + sage: P.push(3, 33) + sage: P.push(1, 10) + sage: P.value(3) + 33 + sage: P.value(7) + Traceback (most recent call last): + ... + ValueError: 7 is not in the heap + """ + if item not in self: + raise ValueError(f"{item} is not in the heap") + return self.nodes[item].value + + +# ============================================================================== +# Class PairingHeap_of_n_hashables +# ============================================================================== + +cdef class PairingHeap_of_n_hashables(PairingHeap_class): + r""" + Pairing Heap for ``n`` hashable items. + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_hashables + sage: P = PairingHeap_of_n_hashables(5); P + PairingHeap_of_n_hashables: capacity 5, size 0 + sage: P.push(1, 3) + sage: P.push('abc', 2) + sage: P + PairingHeap_of_n_hashables: capacity 5, size 2 + sage: P.top() + ('abc', 2) + sage: P.decrease(1, 1) + sage: P.top() + (1, 1) + sage: P.pop() + sage: P.top() + ('abc', 2) + + sage: P = PairingHeap_of_n_hashables(5) + sage: P.push(1, (2, 3)) + sage: P.push('a', (2, 2)) + sage: P.push('b', (3, 3)) + sage: P.push('c', (2, 1)) + sage: P.top() + ('c', (2, 1)) + sage: P.push(Graph(2, immutable=True), (1, 7)) + sage: P.top() + (Graph on 2 vertices, (1, 7)) + sage: P.decrease('b', (1, 5)) + sage: P.top() + ('b', (1, 5)) + + TESTS:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_hashables + sage: P = PairingHeap_of_n_hashables(0) + Traceback (most recent call last): + ... + ValueError: the capacity of the heap must be strictly positive + sage: P = PairingHeap_of_n_hashables(1); P + PairingHeap_of_n_hashables: capacity 1, size 0 + sage: P.push(11, 3) + sage: P.push(12, 4) + Traceback (most recent call last): + ... + ValueError: the heap is full + """ + def __init__(self, size_t n): + r""" + Construct the ``PairingHeap_of_n_hashables``. + + This pairing heap has a maximum capacity of `n` items and each item is a + hashable object. + + INPUT: + + - ``n`` -- strictly positive integer; the maximum number of items in the heap + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_hashables + sage: P = PairingHeap_of_n_hashables(2); P + PairingHeap_of_n_hashables: capacity 2, size 0 + sage: P.push(1, 2) + sage: P + PairingHeap_of_n_hashables: capacity 2, size 1 + sage: P.push(2, 3) + sage: P + PairingHeap_of_n_hashables: capacity 2, size 2 + sage: P.full() + True + sage: P.push(10, 1) + Traceback (most recent call last): + ... + ValueError: the heap is full + sage: P.pop() + sage: P + PairingHeap_of_n_hashables: capacity 2, size 1 + sage: P.push(10, 1) + + TESTS:: + + sage: P = PairingHeap_of_n_hashables(0) + Traceback (most recent call last): + ... + ValueError: the capacity of the heap must be strictly positive + sage: P = PairingHeap_of_n_hashables(1); P + PairingHeap_of_n_hashables: capacity 1, size 0 + """ + if not n: + raise ValueError("the capacity of the heap must be strictly positive") + self.name = "PairingHeap_of_n_hashables" + self.n = n + self.root = NULL + self.nodes = check_allocarray(n, sizeof(PairingHeapNode)) + bitset_init(self.active, n) + bitset_clear(self.active) + self.number_of_items = 0 + self._int_to_item = [None] * n + self._item_to_int = dict() + + cpdef void push(self, object item, object value) except *: + r""" + Insert an item into the heap with specified value (priority). + + INPUT: + + - ``item`` -- non negative integer; the item to consider + + - ``value`` -- the value associated with ``item`` + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_hashables + sage: P = PairingHeap_of_n_hashables(5) + sage: P.push(1, 2) + sage: P.top() + (1, 2) + sage: P.push(3, 1) + sage: P.top() + (3, 1) + + TESTS:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_hashables + sage: P = PairingHeap_of_n_hashables(2) + sage: P.push(1, 2) + sage: P.push(1, 2) + Traceback (most recent call last): + ... + ValueError: 1 is already in the heap + sage: P.push(11, 2) + sage: P.push(7, 5) + Traceback (most recent call last): + ... + ValueError: the heap is full + """ + if item in self: + raise ValueError(f"{item} is already in the heap") + if self.full(): + raise ValueError("the heap is full") + + cdef size_t idx = bitset_first_in_complement(self.active) + self._int_to_item[idx] = item + self._item_to_int[item] = idx + cdef PairingHeapNode * p = self.nodes + idx + Py_INCREF(value) + p.value = value + p.prev = p.succ = p.child = NULL + if self.root == NULL: + self.root = p + else: + self.root = _merge(self.root, p) + bitset_add(self.active, idx) + self.number_of_items += 1 + + cpdef tuple top(self) except *: + r""" + Return the top pair (item, value) of the heap. + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_hashables + sage: P = PairingHeap_of_n_hashables(5) + sage: P.push(1, 2) + sage: P.top() + (1, 2) + sage: P.push(3, 1) + sage: P.top() + (3, 1) + + sage: P = PairingHeap_of_n_hashables(3) + sage: P.top() + Traceback (most recent call last): + ... + ValueError: trying to access the top of an empty heap + """ + if not self: + raise ValueError("trying to access the top of an empty heap") + cdef size_t idx = self.root - self.nodes + return self._int_to_item[idx], self.root.value + + cpdef object top_item(self) except *: + r""" + Return the top item of the heap. + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_hashables + sage: P = PairingHeap_of_n_hashables(5) + sage: P.push(1, 2) + sage: P.top() + (1, 2) + sage: P.top_item() + 1 + + sage: P = PairingHeap_of_n_hashables(3) + sage: P.top_item() + Traceback (most recent call last): + ... + ValueError: trying to access the top of an empty heap + """ + if not self: + raise ValueError("trying to access the top of an empty heap") + cdef size_t idx = self.root - self.nodes + return self._int_to_item[idx] + + cpdef object top_value(self) except *: + r""" + Return the value of the top item of the heap. + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_hashables + sage: P = PairingHeap_of_n_hashables(5) + sage: P.push(1, 2) + sage: P.top() + (1, 2) + sage: P.top_value() + 2 + + sage: P = PairingHeap_of_n_hashables(3) + sage: P.top_value() + Traceback (most recent call last): + ... + ValueError: trying to access the top of an empty heap + """ + if not self: + raise ValueError("trying to access the top of an empty heap") + return self.root.value + + cpdef void pop(self) noexcept: + r""" + Remove the top item from the heap. + + If the heap is already empty, we do nothing. + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_hashables + sage: P = PairingHeap_of_n_hashables(5); len(P) + 0 + sage: P.push(1, 2); len(P) + 1 + sage: P.push(2, 3); len(P) + 2 + sage: P.pop(); len(P) + 1 + sage: P.pop(); len(P) + 0 + sage: P.pop(); len(P) + 0 + """ + if not self: + return + cdef object item = self.top_item() + cdef size_t idx = self._item_to_int[item] + Py_XDECREF(self.nodes[idx].value) + bitset_remove(self.active, idx) + del self._item_to_int[item] + self.number_of_items -= 1 + self.root = _pair(self.root.child) + + cpdef void decrease(self, object item, object new_value) except *: + r""" + Decrease the value of specified item. + + This method is more permissive than it should as it can also be used to + push an item in the heap. + + INPUT: + + - ``item`` -- the item to consider + + - ``new_value`` -- the new value for ``item`` + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_hashables + sage: P = PairingHeap_of_n_hashables(5) + sage: 3 in P + False + sage: P.decrease(3, 33) + sage: 3 in P + True + sage: P.top() + (3, 33) + sage: P.push(1, 10) + sage: P.top() + (1, 10) + sage: P.decrease(3, 7) + sage: P.top() + (3, 7) + + TESTS:: + + sage: P = PairingHeap_of_n_hashables(5) + sage: P.push(1, 3) + sage: P.decrease(1, 2) + sage: P.decrease(1, 2) + Traceback (most recent call last): + ... + ValueError: the new value must be less than the current value + """ + cdef PairingHeapNode * p + cdef size_t idx + if item in self: + idx = self._item_to_int[item] + p = self.nodes + idx + if p.value <= new_value: + raise ValueError("the new value must be less than the current value") + Py_XDECREF(p.value) + Py_INCREF(new_value) + p.value = new_value + if p.prev != NULL: + _unlink(p) + self.root = _merge(self.root, p) + else: + self.push(item, new_value) + + def __contains__(self, object item): + r""" + Check whether the specified item is in the heap. + + INPUT: + + - ``item`` -- the item to consider + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_hashables + sage: P = PairingHeap_of_n_hashables(5) + sage: 3 in P + False + sage: P.push(3, 33) + sage: 3 in P + True + sage: 100 in P + False + """ + return item in self._item_to_int + + contains = __contains__ + + cpdef object value(self, object item) except *: + r""" + Return the value associated with the item. + + INPUT: + + - ``item`` -- the item to consider + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_hashables + sage: P = PairingHeap_of_n_hashables(5) + sage: P.push(3, 33) + sage: P.push(1, 10) + sage: P.value(3) + 33 + sage: P.value(7) + Traceback (most recent call last): + ... + ValueError: 7 is not in the heap + """ + if item not in self: + raise ValueError(f"{item} is not in the heap") + cdef size_t idx = self._item_to_int[item] + return self.nodes[idx].value + + +# ============================================================================== +# Methods to check the validity of the pairing heaps +# ============================================================================== + +def _test_PairingHeap_from_C(n=100): + r""" + Test :class:`~sage.data_structures.pairing_heap.PairingHeap`. + + INPUT: + + - ``n`` -- a strictly positive integer (default: 100); the maximum capacity + of the heap + + TESTS:: + + sage: from sage.data_structures.pairing_heap import _test_PairingHeap_from_C + sage: _test_PairingHeap_from_C(100) + """ + from sage.misc.prandom import randint, shuffle + sig_on() + cdef PairingHeap[size_t, size_t] *PH = new PairingHeap[size_t, size_t]() + sig_off() + + # Initialize a list of tuples (value, item) randomly ordered + items = list(range(n)) + values = list(range(n)) + shuffle(items) + shuffle(values) + cdef list Lref = list(zip(values, items)) + + for value, item in Lref: + PH.push(item, value) + sig_check() + + L = [] + while not PH.empty(): + item, value = PH.top() + L.append((value, item)) + PH.pop() + sig_check() + + if L != sorted(Lref): + raise ValueError('the order is not good') + + # Test decrease key operations. We first push items in the heap with an + # excess of k in the value. Then we decrease the keys in a random order by + # random values until returning to the origianl values. We finally check the + # validity of the resulting ordering. + k = 10 + dec = {item: k for item in items} + shuffle(Lref) + for value, item in Lref: + PH.push(item, value + k) + sig_check() + + L = list(items) + while L: + i = randint(0, len(L) - 1) + item = L[i] + d = randint(1, dec[item]) + dec[item] -= d + if not dec[item]: + L[i] = L[-1] + L.pop() + PH.decrease(item, PH.value(item) - d) + sig_check() + + L = [] + while not PH.empty(): + item, value = PH.top() + L.append((value, item)) + PH.pop() + sig_check() + + if L != sorted(Lref): + raise ValueError('the order is not good') + + sig_on() + sig_free(PH) + sig_off() + + sig_on() + cdef PairingHeap[pair[size_t, size_t], size_t] *Q = new PairingHeap[pair[size_t, size_t], size_t]() + sig_off() + + # Initialize a list of tuples (value, item) randomly ordered + items = [(i, i + 1) for i in range(n)] + values = list(range(n)) + shuffle(items) + shuffle(values) + Lref = list(zip(values, items)) + + for value, item in Lref: + Q.push(item, value) + sig_check() + + L = [] + while not Q.empty(): + item, value = Q.top() + L.append((value, item)) + Q.pop() + sig_check() + + if L != sorted(Lref): + raise ValueError('the order is not good') + + # Test decrease key operations. We first push items in the heap with an + # excess of k in the value. Then we decrease the keys in a random order by + # random values until returning to the origianl values. We finally check the + # validity of the resulting ordering. + k = 10 + dec = {item: k for item in items} + shuffle(Lref) + for value, item in Lref: + Q.push(item, value + k) + sig_check() + + L = list(items) + while L: + i = randint(0, len(L) - 1) + item = L[i] + d = randint(1, dec[item]) + dec[item] -= d + if not dec[item]: + L[i] = L[-1] + L.pop() + Q.decrease(item, Q.value(item) - d) + sig_check() + + L = [] + while not Q.empty(): + item, value = Q.top() + L.append((value, item)) + Q.pop() + sig_check() + + if L != sorted(Lref): + raise ValueError('the order is not good') + + sig_on() + sig_free(Q) + sig_off() + + # Different cost function + from sage.functions.trig import sin, cos + sig_on() + cdef PairingHeap[pair[size_t, size_t], pair[size_t, size_t]] HH = PairingHeap[pair[size_t, size_t], pair[size_t, size_t]]() + sig_off() + + for i in range(n): + HH.push((i, i + 1), (sin(i), cos(i))) + sig_check() + + L = [] + while not HH.empty(): + L.append(HH.top()) + HH.pop() + sig_check() + + for (u, cu), (v, cv) in zip(L, L[1:]): + if cu > cv: + print(u, cu, v, cv) + + # We finally show that an error is raised when trying to access the top of + # an empty heap + try: + _ = HH.top() + print("something goes wrong, the error has not been raised") + except ValueError, msg: + # The error has been properly handled + pass + + try: + _ = HH.top_item() + print("something goes wrong, the error has not been raised") + except ValueError, msg: + # The error has been properly handled + pass + + try: + _ = HH.top_value() + print("something goes wrong, the error has not been raised") + except ValueError, msg: + # The error has been properly handled + pass + + # Or to get the value associated to an item that is not in the heap + try: + _ = HH.value((123, 456)) + print("something goes wrong, the error has not been raised") + except ValueError, msg: + # The error has been properly handled + pass + + + +def _test_PairingHeap_of_n_integers(n=100): + r""" + Test :class:`~sage.data_structures.pairing_heap.PairingHeap_of_n_integers`. + + INPUT: + + - ``n`` -- a strictly positive integer (default: 100); the maximum capacity + of the heap + + TESTS:: + + sage: from sage.data_structures.pairing_heap import _test_PairingHeap_of_n_integers + sage: _test_PairingHeap_of_n_integers(100) + """ + from sage.misc.prandom import randint, shuffle + + sig_on() + cdef PairingHeap_of_n_integers P = PairingHeap_of_n_integers(n) + sig_off() + + # Initialize a list of tuples (value, item) randomly ordered + cdef list items = list(range(n)) + cdef list values = list(range(n)) + shuffle(items) + shuffle(values) + cdef list Lref = list(zip(values, items)) + + cdef int value, item + for value, item in Lref: + P.push(item, value) + sig_check() + + cdef list L = [] + while P: + item, value = P.top() + L.append((value, item)) + P.pop() + sig_check() + + if L != sorted(Lref): + raise ValueError('the order is not good') + + # Test decrease key operations. We first push items in the heap with an + # excess of k in the value. Then we decrease the keys in a random order by + # random values until returning to the origianl values. We finally check the + # validity of the resulting ordering. + cdef int k = 10 + cdef list dec = [k] * n + shuffle(Lref) + for value, item in Lref: + P.push(item, value + k) + sig_check() + + L = list(items) + while L: + i = randint(0, len(L) - 1) + item = L[i] + d = randint(1, dec[item]) + dec[item] -= d + if not dec[item]: + L[i] = L[-1] + L.pop() + P.decrease(item, P.value(item) - d) + sig_check() + + L = [] + while P: + item, value = P.top() + L.append((value, item)) + P.pop() + sig_check() + + if L != sorted(Lref): + raise ValueError('the order is not good') + + # We finally show that an error is raised when trying to access the top of + # an empty heap + try: + _ = P.top() + print("something goes wrong, the error has not been raised") + except ValueError, msg: + # The error has been properly handled + pass + + try: + _ = P.top_item() + print("something goes wrong, the error has not been raised") + except ValueError, msg: + # The error has been properly handled + pass + + try: + _ = P.top_value() + print("something goes wrong, the error has not been raised") + except ValueError, msg: + # The error has been properly handled + pass + + # Or to get the value associated to an item that is not in the heap + try: + _ = P.value(123) + print("something goes wrong, the error has not been raised") + except ValueError, msg: + # The error has been properly handled + pass + + +def _test_PairingHeap_of_n_hashables(n=100): + r""" + Test :class:`~sage.data_structures.pairing_heap.PairingHeap_of_n_hashables`. + + INPUT: + + - ``n`` -- a strictly positive integer (default: 100); the maximum capacity + of the heap + + TESTS:: + + sage: from sage.data_structures.pairing_heap import _test_PairingHeap_of_n_hashables + sage: _test_PairingHeap_of_n_hashables(100) + """ + from sage.misc.prandom import randint, shuffle + + sig_on() + cdef PairingHeap_of_n_hashables P = PairingHeap_of_n_hashables(n) + sig_off() + + # Initialize a list of tuples (value, item) randomly ordered + cdef list items = [(str(i), i) for i in range(n)] + cdef list values = list(range(n)) + shuffle(items) + shuffle(values) + cdef list Lref = list(zip(values, items)) + + for value, item in Lref: + P.push(item, value) + sig_check() + + cdef list L = [] + while P: + item, value = P.top() + L.append((value, item)) + P.pop() + sig_check() + + if L != sorted(Lref): + raise ValueError('the order is not good') + + # Test decrease key operations. We first push items in the heap with an + # excess of k in the value. Then we decrease the keys in a random order by + # random values until returning to the origianl values. We finally check the + # validity of the resulting ordering. + cdef int k = 10 + cdef dict dec = {item: k for item in items} + shuffle(Lref) + for value, item in Lref: + P.push(item, value + k) + sig_check() + + L = list(items) + while L: + i = randint(0, len(L) - 1) + item = L[i] + d = randint(1, dec[item]) + dec[item] -= d + if not dec[item]: + L[i] = L[-1] + L.pop() + P.decrease(item, P.value(item) - d) + sig_check() + + L = [] + while P: + item, value = P.top() + L.append((value, item)) + P.pop() + sig_check() + + if L != sorted(Lref): + raise ValueError('the order is not good') + + # We finally show that an error is raised when trying to access the top of + # an empty heap + try: + _ = P.top() + print("something goes wrong, the error has not been raised") + except: + # The error has been properly handled + pass + + try: + _ = P.top_item() + print("something goes wrong, the error has not been raised") + except ValueError, msg: + # The error has been properly handled + pass + + try: + _ = P.top_value() + print("something goes wrong, the error has not been raised") + except ValueError, msg: + # The error has been properly handled + pass + + # Or to get the value associated to an item that is not in the heap + try: + _ = P.value(123) + print("something goes wrong, the error has not been raised") + except ValueError, msg: + # The error has been properly handled + pass + + +def compare_heaps(n=100, verbose=False): + r""" + Check that the heaps behave the same. + + This method selects a list of instructions: push items in some order, + decrease the values of the items in some order, extract all items in order. + Then it applies the same instructions to a ``PairingHeap``, a + :class:`PairingHeap_of_n_integers` and a + :class:`PairingHeap_of_n_hashables`. It checks that all heaps report the + list of items in the same order and it measures the running time. + + INPUT: + + - ``n`` -- a strictly positive integer (default: 100); the maximum capacity + of the heap + + - ``verbose`` -- boolean (default: ``False``); whether to display + information about the running times + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import compare_heaps + sage: compare_heaps(n=100) + sage: compare_heaps(n=100, verbose=True) # random + PairingHeap_of_n_integers: 7.300000000043383e-05 + PairingHeap_of_n_hashables: 9.70000000037885e-05 + PairingHeap (C++): 9.599999999920783e-05 + sage: compare_heaps(1000000, verbose=True) # not tested (long time), random + PairingHeap_of_n_integers: 1.5988719999999996 + PairingHeap_of_n_hashables: 5.039089999999998 + PairingHeap (C++): 3.3256689999999995 + """ + from sage.misc.prandom import shuffle + from sage.misc.timing import cputime + + items = list(range(n)) + values = list(range(n)) + shuffle(items) + shuffle(values) + Lref = list(zip(values, items)) + k = 10 + dec_order = list(items) + shuffle(dec_order) + + t = cputime() + sig_on() + cdef PairingHeap_of_n_integers P = PairingHeap_of_n_integers(n) + sig_off() + for value, item in Lref: + P.push(item, value + k) + sig_check() + for item in dec_order: + P.decrease(item, P.value(item) - k) + sig_check() + LP = [] + while P: + LP.append(P.top()) + P.pop() + sig_check() + t = cputime(t) + if verbose: + print(f"PairingHeap_of_n_integers: {t}") + + t = cputime() + sig_on() + cdef PairingHeap_of_n_hashables Q = PairingHeap_of_n_hashables(n) + sig_off() + for value, item in Lref: + Q.push(item, value + k) + sig_check() + for item in dec_order: + Q.decrease(item, Q.value(item) - k) + sig_check() + LQ = [] + while Q: + LQ.append(Q.top()) + Q.pop() + sig_check() + t = cputime(t) + if verbose: + print(f"PairingHeap_of_n_hashables: {t}") + + t = cputime() + sig_on() + cdef PairingHeap[size_t, size_t] PH = PairingHeap[size_t, size_t]() + sig_off() + for value, item in Lref: + PH.push(item, value + k) + sig_check() + for item in dec_order: + PH.decrease(item, PH.value(item) - k) + sig_check() + LPH = [] + while not PH.empty(): + LPH.append(PH.top()) + PH.pop() + sig_check() + t = cputime(t) + if verbose: + print(f"PairingHeap (C++): {t}") + + if LPH != LP or LP != LQ: + print('something goes wrong') From aaf0502f23f189d55d60bd0225ccf6c6a890b137 Mon Sep 17 00:00:00 2001 From: dcoudert Date: Wed, 27 Nov 2024 20:14:01 +0100 Subject: [PATCH 02/27] fix lint errors --- src/sage/data_structures/pairing_heap.pyx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/sage/data_structures/pairing_heap.pyx b/src/sage/data_structures/pairing_heap.pyx index e320138200f..2ec378e4041 100644 --- a/src/sage/data_structures/pairing_heap.pyx +++ b/src/sage/data_structures/pairing_heap.pyx @@ -138,7 +138,7 @@ cdef inline PairingHeapNode * _pair(PairingHeapNode * p) except *: """ if p == NULL: return NULL - + # Move toward the end of the list, counting elements along the way. # This is done in order to: # - know whether the list has odd or even number of nodes @@ -148,7 +148,7 @@ cdef inline PairingHeapNode * _pair(PairingHeapNode * p) except *: while it.succ != NULL: it = it.succ children += 1 - + cdef PairingHeapNode * result cdef PairingHeapNode * a cdef PairingHeapNode * b @@ -164,7 +164,7 @@ cdef inline PairingHeapNode * _pair(PairingHeapNode * p) except *: it = it.prev.prev a.prev = a.succ = b.prev = b.succ = NULL result = _merge(a, b) - + for _ in range((children - 1) // 2): a = it b = it.prev @@ -647,7 +647,7 @@ cdef class PairingHeap_of_n_integers(PairingHeap_class): return bitset_in(self.active, item) contains = __contains__ - + cpdef object value(self, size_t item) except *: r""" Return the value associated with the item. @@ -1025,7 +1025,7 @@ cdef class PairingHeap_of_n_hashables(PairingHeap_class): return item in self._item_to_int contains = __contains__ - + cpdef object value(self, object item) except *: r""" Return the value associated with the item. @@ -1211,7 +1211,7 @@ def _test_PairingHeap_from_C(n=100): L.append(HH.top()) HH.pop() sig_check() - + for (u, cu), (v, cv) in zip(L, L[1:]): if cu > cv: print(u, cu, v, cv) @@ -1248,7 +1248,6 @@ def _test_PairingHeap_from_C(n=100): pass - def _test_PairingHeap_of_n_integers(n=100): r""" Test :class:`~sage.data_structures.pairing_heap.PairingHeap_of_n_integers`. @@ -1435,7 +1434,7 @@ def _test_PairingHeap_of_n_hashables(n=100): try: _ = P.top() print("something goes wrong, the error has not been raised") - except: + except ValueError, msg: # The error has been properly handled pass From 747cc75d80a43b7974bcc2803cb9aba29ae269bc Mon Sep 17 00:00:00 2001 From: dcoudert Date: Wed, 27 Nov 2024 20:20:06 +0100 Subject: [PATCH 03/27] fix error in src/doc/en/reference/references/index.rst --- src/doc/en/reference/references/index.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 17accf462db..8d59326604d 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -2762,9 +2762,9 @@ REFERENCES: Cambridge University Press, Cambridge, 2009. See also the `Errata list `_. -.. [FSST1986] Michael L. Fredman, Robert Sedgewick, Daniel D. Sleator, and - Robert E. Tarjan. *The pairing heap: A new form of self-adjusting - heap*, Algorithmica 1 (1986), 111-129. +.. [FSST1986] Michael L. Fredman, Robert Sedgewick, Daniel D. Sleator, + and Robert E. Tarjan. *The pairing heap: A new form of + self-adjusting heap*, Algorithmica, 1:111-129, 1986. :doi:`10.1007/BF01840439` .. [FST2012] \A. Felikson, \M. Shapiro, and \P. Tumarkin, *Cluster Algebras of From f3a88f5bcb74075675ddbb5512b9dd3727dd8d30 Mon Sep 17 00:00:00 2001 From: dcoudert Date: Wed, 27 Nov 2024 20:36:28 +0100 Subject: [PATCH 04/27] remove some trailing whitespaces --- src/sage/data_structures/pairing_heap.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/data_structures/pairing_heap.pyx b/src/sage/data_structures/pairing_heap.pyx index 2ec378e4041..becef12d572 100644 --- a/src/sage/data_structures/pairing_heap.pyx +++ b/src/sage/data_structures/pairing_heap.pyx @@ -186,7 +186,7 @@ cdef inline PairingHeapNode * _merge(PairingHeapNode * a, PairingHeapNode * b) e """ if _compare(a, b): # True if a.value <= b.value _link(a, b) - return a + return a _link(b, a) return b @@ -206,7 +206,7 @@ cdef inline _link(PairingHeapNode * a, PairingHeapNode * b) except *: b.prev = a a.child = b - + cdef inline _unlink(PairingHeapNode * p) except *: r""" Remove ``p`` from the list of children of its parent. From ac0ef3e044ed1550f0efd94696fe081dbbcb80e6 Mon Sep 17 00:00:00 2001 From: dcoudert Date: Wed, 27 Nov 2024 22:21:17 +0100 Subject: [PATCH 05/27] try to fix references --- src/doc/en/reference/references/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index 8d59326604d..17c91611b68 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -2765,7 +2765,7 @@ REFERENCES: .. [FSST1986] Michael L. Fredman, Robert Sedgewick, Daniel D. Sleator, and Robert E. Tarjan. *The pairing heap: A new form of self-adjusting heap*, Algorithmica, 1:111-129, 1986. - :doi:`10.1007/BF01840439` + :doi:`10.1007/BF01840439` .. [FST2012] \A. Felikson, \M. Shapiro, and \P. Tumarkin, *Cluster Algebras of Finite Mutation Type Via Unfoldings*, Int Math Res Notices (2012) From bd63d91fbc9bac20e1cd26527318073ffdacd51a Mon Sep 17 00:00:00 2001 From: dcoudert Date: Wed, 27 Nov 2024 23:55:54 +0100 Subject: [PATCH 06/27] meson build --- src/sage/data_structures/meson.build | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/sage/data_structures/meson.build b/src/sage/data_structures/meson.build index 8a94548917b..124a209dbbe 100644 --- a/src/sage/data_structures/meson.build +++ b/src/sage/data_structures/meson.build @@ -9,6 +9,8 @@ py.install_sources( 'bounded_integer_sequences.pxd', 'list_of_pairs.pxd', 'mutable_poset.py', + 'pairing_heap.h', + 'pairing_heap.pxd', 'sparse_bitset.pxd', 'stream.py', subdir: 'sage/data_structures', @@ -21,6 +23,7 @@ extension_data = { 'blas_dict' : files('blas_dict.pyx'), 'bounded_integer_sequences' : files('bounded_integer_sequences.pyx'), 'list_of_pairs' : files('list_of_pairs.pyx'), + 'pairing_heap' : files('pairing_heap.pyx'), } foreach name, pyx : extension_data From 7d2982e769399e2e717591fa59d735c35862b8ac Mon Sep 17 00:00:00 2001 From: dcoudert Date: Wed, 27 Nov 2024 23:58:10 +0100 Subject: [PATCH 07/27] remove useless reference --- src/sage/data_structures/pairing_heap.pyx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/sage/data_structures/pairing_heap.pyx b/src/sage/data_structures/pairing_heap.pyx index becef12d572..b5480a89c1e 100644 --- a/src/sage/data_structures/pairing_heap.pyx +++ b/src/sage/data_structures/pairing_heap.pyx @@ -85,10 +85,6 @@ AUTHORS: - David Coudert (2024) - Initial version. - -[1] M. L. Fredman, R. Sedgewick, D. D. Sleator, and R. E. Tarjan. - "The pairing heap: a new form of self-adjusting heap". - Algorithmica. 1 (1): 111-129, 1986. doi:10.1007/BF01840439. """ # ****************************************************************************** # Copyright (C) 2024 David Coudert From f0d2c3ae31b12e7aa239dc3bbb0ad19b05499658 Mon Sep 17 00:00:00 2001 From: dcoudert Date: Thu, 28 Nov 2024 10:40:08 +0100 Subject: [PATCH 08/27] detail in pairing_heap.pyx --- src/sage/data_structures/pairing_heap.pyx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/data_structures/pairing_heap.pyx b/src/sage/data_structures/pairing_heap.pyx index b5480a89c1e..4f6234236e6 100644 --- a/src/sage/data_structures/pairing_heap.pyx +++ b/src/sage/data_structures/pairing_heap.pyx @@ -1069,7 +1069,7 @@ def _test_PairingHeap_from_C(n=100): """ from sage.misc.prandom import randint, shuffle sig_on() - cdef PairingHeap[size_t, size_t] *PH = new PairingHeap[size_t, size_t]() + cdef PairingHeap[size_t, size_t] * PH = new PairingHeap[size_t, size_t]() sig_off() # Initialize a list of tuples (value, item) randomly ordered @@ -1131,7 +1131,7 @@ def _test_PairingHeap_from_C(n=100): sig_off() sig_on() - cdef PairingHeap[pair[size_t, size_t], size_t] *Q = new PairingHeap[pair[size_t, size_t], size_t]() + cdef PairingHeap[pair[size_t, size_t], size_t] * Q = new PairingHeap[pair[size_t, size_t], size_t]() sig_off() # Initialize a list of tuples (value, item) randomly ordered @@ -1195,7 +1195,7 @@ def _test_PairingHeap_from_C(n=100): # Different cost function from sage.functions.trig import sin, cos sig_on() - cdef PairingHeap[pair[size_t, size_t], pair[size_t, size_t]] HH = PairingHeap[pair[size_t, size_t], pair[size_t, size_t]]() + cdef PairingHeap[pair[size_t, size_t], pair[size_t, size_t]] * HH = new PairingHeap[pair[size_t, size_t], pair[size_t, size_t]]() sig_off() for i in range(n): From fe3751dc7ac1b976a49a3a3c2b0f4881064557cb Mon Sep 17 00:00:00 2001 From: dcoudert Date: Thu, 28 Nov 2024 14:54:54 +0100 Subject: [PATCH 09/27] use unordered_map --- src/sage/data_structures/pairing_heap.h | 13 ++-- src/sage/data_structures/pairing_heap.pyx | 83 +++-------------------- 2 files changed, 18 insertions(+), 78 deletions(-) diff --git a/src/sage/data_structures/pairing_heap.h b/src/sage/data_structures/pairing_heap.h index dbcb99bc1fc..1dd30e54144 100644 --- a/src/sage/data_structures/pairing_heap.h +++ b/src/sage/data_structures/pairing_heap.h @@ -5,9 +5,11 @@ * for more details. * * This implementation is templated by the type TI of items and the type TV of - * the value associated with an item. The top of the heap is the item with - * smallest value, i.e., this is a min heap data structure. The number of items - * in the heap is not fixed. It supports the following operations: + * the value associated with an item. The type TI must be either a standard type + * (int, size_t, etc.) or a type equipped with a has function as supported by + * std::unordered_map. The top of the heap is the item with smallest value, + * i.e., this is a min heap data structure. The number of items in the heap is + * not fixed. It supports the following operations: * * - empty(): return true if the heap is empty, and false otherwise. * @@ -53,9 +55,8 @@ #define PAIRING_HEAP_H #include -#include +#include #include -#include namespace pairing_heap { @@ -140,7 +141,7 @@ namespace pairing_heap { PairingHeapNode *root; // Map used to access stored items - std::map *> nodes; + std::unordered_map *> nodes; // Pair list of heaps and return pointer to the top of resulting heap PairingHeapNode *_pair(PairingHeapNode *p); diff --git a/src/sage/data_structures/pairing_heap.pyx b/src/sage/data_structures/pairing_heap.pyx index 4f6234236e6..0364b049b78 100644 --- a/src/sage/data_structures/pairing_heap.pyx +++ b/src/sage/data_structures/pairing_heap.pyx @@ -16,7 +16,8 @@ min-heap data structure. - ``PairingHeap``: interface to a pairing heap data structure written in C++. The advantages of this data structure are that: its capacity is unbounded; - items can be of any hashable type; values can be of any specified type + items can be of any hashable type equipped with a hashing method that can be + supported by ``std::unordered_map``; values can be of any specified type equipped with a comparison method (``<=``). This data structure is for internal use and therefore cannot be accessed from a shell. @@ -1130,76 +1131,14 @@ def _test_PairingHeap_from_C(n=100): sig_free(PH) sig_off() - sig_on() - cdef PairingHeap[pair[size_t, size_t], size_t] * Q = new PairingHeap[pair[size_t, size_t], size_t]() - sig_off() - - # Initialize a list of tuples (value, item) randomly ordered - items = [(i, i + 1) for i in range(n)] - values = list(range(n)) - shuffle(items) - shuffle(values) - Lref = list(zip(values, items)) - - for value, item in Lref: - Q.push(item, value) - sig_check() - - L = [] - while not Q.empty(): - item, value = Q.top() - L.append((value, item)) - Q.pop() - sig_check() - - if L != sorted(Lref): - raise ValueError('the order is not good') - - # Test decrease key operations. We first push items in the heap with an - # excess of k in the value. Then we decrease the keys in a random order by - # random values until returning to the origianl values. We finally check the - # validity of the resulting ordering. - k = 10 - dec = {item: k for item in items} - shuffle(Lref) - for value, item in Lref: - Q.push(item, value + k) - sig_check() - - L = list(items) - while L: - i = randint(0, len(L) - 1) - item = L[i] - d = randint(1, dec[item]) - dec[item] -= d - if not dec[item]: - L[i] = L[-1] - L.pop() - Q.decrease(item, Q.value(item) - d) - sig_check() - - L = [] - while not Q.empty(): - item, value = Q.top() - L.append((value, item)) - Q.pop() - sig_check() - - if L != sorted(Lref): - raise ValueError('the order is not good') - - sig_on() - sig_free(Q) - sig_off() - # Different cost function from sage.functions.trig import sin, cos sig_on() - cdef PairingHeap[pair[size_t, size_t], pair[size_t, size_t]] * HH = new PairingHeap[pair[size_t, size_t], pair[size_t, size_t]]() + cdef PairingHeap[size_t, pair[size_t, size_t]] * HH = new PairingHeap[size_t, pair[size_t, size_t]]() sig_off() for i in range(n): - HH.push((i, i + 1), (sin(i), cos(i))) + HH.push(i, (sin(i), cos(i))) sig_check() L = [] @@ -1237,7 +1176,7 @@ def _test_PairingHeap_from_C(n=100): # Or to get the value associated to an item that is not in the heap try: - _ = HH.value((123, 456)) + _ = HH.value(123) print("something goes wrong, the error has not been raised") except ValueError, msg: # The error has been properly handled @@ -1481,13 +1420,13 @@ def compare_heaps(n=100, verbose=False): sage: from sage.data_structures.pairing_heap import compare_heaps sage: compare_heaps(n=100) sage: compare_heaps(n=100, verbose=True) # random - PairingHeap_of_n_integers: 7.300000000043383e-05 - PairingHeap_of_n_hashables: 9.70000000037885e-05 - PairingHeap (C++): 9.599999999920783e-05 + PairingHeap_of_n_integers: 7.800000000024454e-05 + PairingHeap_of_n_hashables: 9.400000000026054e-05 + PairingHeap (C++): 6.899999999987472e-05 sage: compare_heaps(1000000, verbose=True) # not tested (long time), random - PairingHeap_of_n_integers: 1.5988719999999996 - PairingHeap_of_n_hashables: 5.039089999999998 - PairingHeap (C++): 3.3256689999999995 + PairingHeap_of_n_integers: 1.5106779999999995 + PairingHeap_of_n_hashables: 4.998040000000001 + PairingHeap (C++): 1.7841750000000012 """ from sage.misc.prandom import shuffle from sage.misc.timing import cputime From 2804a777b61fc049c16a687deb29e8a33a9c8743 Mon Sep 17 00:00:00 2001 From: dcoudert Date: Fri, 29 Nov 2024 18:43:16 +0100 Subject: [PATCH 10/27] #39046: improvements in pairing_heap.h --- src/sage/data_structures/pairing_heap.h | 32 +++++++++---------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/src/sage/data_structures/pairing_heap.h b/src/sage/data_structures/pairing_heap.h index 1dd30e54144..29845894650 100644 --- a/src/sage/data_structures/pairing_heap.h +++ b/src/sage/data_structures/pairing_heap.h @@ -104,7 +104,7 @@ namespace pairing_heap { virtual ~PairingHeap(); // Return true if the heap is empty, else false - bool empty() { return root == nullptr; } + bool empty() const { return root == nullptr; } // Return true if the heap is empty, else false explicit operator bool() const { return root != nullptr; } @@ -113,13 +113,13 @@ namespace pairing_heap { void push(const TI &some_item, const TV &some_value); // Return the top pair (item, value) of the heap - std::pair top(); + std::pair top() const; // Return the top item of the heap - TI top_item(); + TI top_item() const; // Return the top value of the heap - TV top_value(); + TV top_value() const; // Remove the top element from the heap void pop(); @@ -128,13 +128,15 @@ namespace pairing_heap { void decrease(const TI &some_item, const TV &new_value); // Check if specified item is in the heap - bool contains(TI const& some_item); + bool contains(TI const& some_item) const { + return nodes.find(some_item) != nodes.end(); + } // Return the value associated with the item TV value(const TI &some_item); // Return the number of items in the heap - size_t size() { return nodes.size(); } + size_t size() const { return nodes.size(); } private: // Pointer to the top of the heap @@ -162,7 +164,6 @@ namespace pairing_heap { PairingHeap::PairingHeap(): root(nullptr) { - nodes.clear(); } // Copy constructor @@ -170,7 +171,6 @@ namespace pairing_heap { PairingHeap::PairingHeap(PairingHeap const *other): root(nullptr) { - nodes.clear(); for (auto const& it:other->nodes) push(it.first, it.second->value); } @@ -181,8 +181,6 @@ namespace pairing_heap { { for (auto const& it: nodes) delete it.second; - root = nullptr; - nodes.clear(); } // Insert an item into the heap with specified value (priority) @@ -199,7 +197,7 @@ namespace pairing_heap { // Return the top pair (value, item) of the heap template - inline std::pair PairingHeap::top() + inline std::pair PairingHeap::top() const { if (root == nullptr) { throw std::domain_error("trying to access the top of an empty heap"); @@ -209,7 +207,7 @@ namespace pairing_heap { // Return the top item of the heap template - inline TI PairingHeap::top_item() + inline TI PairingHeap::top_item() const { if (root == nullptr) { throw std::domain_error("trying to access the top of an empty heap"); @@ -219,7 +217,7 @@ namespace pairing_heap { // Return the top value of the heap template - inline TV PairingHeap::top_value() + inline TV PairingHeap::top_value() const { if (root == nullptr) { throw std::domain_error("trying to access the top of an empty heap"); @@ -259,14 +257,6 @@ namespace pairing_heap { } } - // Check if specified item is in the heap - template - inline bool PairingHeap::contains(TI const& some_item) - { - return nodes.find(some_item) != nodes.end(); - // return nodes.count(some_item) > 0; - } - // Return the value associated with the item template inline TV PairingHeap::value(const TI &some_item) From 780dc3333a921844d068edaca17be312055fab3c Mon Sep 17 00:00:00 2001 From: dcoudert Date: Sat, 30 Nov 2024 11:57:11 +0100 Subject: [PATCH 11/27] #39046: improve pairing_heap.h --- src/sage/data_structures/pairing_heap.h | 376 ++++++++++-------------- 1 file changed, 162 insertions(+), 214 deletions(-) diff --git a/src/sage/data_structures/pairing_heap.h b/src/sage/data_structures/pairing_heap.h index 29845894650..f14cbdd81e6 100644 --- a/src/sage/data_structures/pairing_heap.h +++ b/src/sage/data_structures/pairing_heap.h @@ -73,8 +73,9 @@ namespace pairing_heap { PairingHeapNode * next; // Next sibling of the node PairingHeapNode * child; // First child of the node - explicit PairingHeapNode(const TI &some_item, const TV &some_value): - item(some_item), value(some_value), prev(nullptr), next(nullptr), child(nullptr) { + explicit PairingHeapNode(const TI &some_item, const TV &some_value) + : item(some_item), value(some_value), + prev(nullptr), next(nullptr), child(nullptr) { } bool operator<(PairingHeapNode const& other) const { @@ -84,7 +85,7 @@ namespace pairing_heap { bool operator<=(PairingHeapNode const& other) const { return value <= other.value; } - }; + }; // end struct PairingHeapNode template< @@ -94,38 +95,98 @@ namespace pairing_heap { class PairingHeap { public: + // Constructor - explicit PairingHeap(); + explicit PairingHeap() + : root(nullptr) { + } // Copy constructor - PairingHeap(PairingHeap const *other); + PairingHeap(PairingHeap const *other) + : root(nullptr) { + for (auto const& it: other->nodes) { + push(it.first, it.second->value); + } + } // Destructor - virtual ~PairingHeap(); + virtual ~PairingHeap() { + for (auto const& it: nodes) { + delete it.second; + } + } // Return true if the heap is empty, else false - bool empty() const { return root == nullptr; } + bool empty() const { + return root == nullptr; + } - // Return true if the heap is empty, else false - explicit operator bool() const { return root != nullptr; } + // Return true if the heap is not empty, else false + explicit operator bool() const { + return root != nullptr; + } // Insert an item into the heap with specified value (priority) - void push(const TI &some_item, const TV &some_value); + void push(const TI &some_item, const TV &some_value) { + if (nodes.find(some_item) != nodes.end()) { + throw std::invalid_argument("item already in the heap"); + } + PairingHeapNode *p = new PairingHeapNode(some_item, some_value); + nodes[some_item] = p; + root = root == nullptr ? p : _merge(root, p); + } // Return the top pair (item, value) of the heap - std::pair top() const; + std::pair top() const { + if (root == nullptr) { + throw std::domain_error("trying to access the top of an empty heap"); + } + return std::make_pair(root->item, root->value); + } // Return the top item of the heap - TI top_item() const; + TI top_item() const { + if (root == nullptr) { + throw std::domain_error("trying to access the top of an empty heap"); + } + return root->item; + } // Return the top value of the heap - TV top_value() const; + TV top_value() const { + if (root == nullptr) { + throw std::domain_error("trying to access the top of an empty heap"); + } + return root->value; + } - // Remove the top element from the heap - void pop(); + // Remove the top element from the heap. Do nothing if empty + void pop() { + if (root != nullptr) { + PairingHeapNode *p = root->child; + nodes.erase(root->item); + delete root; + root = _pair(p); + } + } // Decrease the value of specified item - void decrease(const TI &some_item, const TV &new_value); + // If the item is not in the heap, push it + void decrease(const TI &some_item, const TV &new_value) { + if (contains(some_item)) { + PairingHeapNode *p = nodes[some_item]; + if (p->value <= new_value) { + throw std::invalid_argument("the new value must be less than the current value"); + } + p->value = new_value; + if (p->prev != nullptr) { + _unlink(p); + root = _merge(root, p); + } + } else { + push(some_item, new_value); + } + } // Check if specified item is in the heap bool contains(TI const& some_item) const { @@ -133,227 +194,114 @@ namespace pairing_heap { } // Return the value associated with the item - TV value(const TI &some_item); + TV value(const TI &some_item) const { + auto it = nodes.find(some_item); + if (it == nodes.end()) { + throw std::invalid_argument("the specified item is not in the heap"); + } + return it->second->value; + } // Return the number of items in the heap - size_t size() const { return nodes.size(); } + size_t size() const { + return nodes.size(); + } private: + // Pointer to the top of the heap PairingHeapNode *root; // Map used to access stored items std::unordered_map *> nodes; - // Pair list of heaps and return pointer to the top of resulting heap - PairingHeapNode *_pair(PairingHeapNode *p); - - // Merge 2 heaps and return pointer to the top of resulting heap - PairingHeapNode *_merge(PairingHeapNode *a, PairingHeapNode *b); - - // Make b a child of a - static void _link(PairingHeapNode *a, PairingHeapNode *b); - // Remove p from its parent children list - static void _unlink(PairingHeapNode *p); - - }; - - // Constructor - template - PairingHeap::PairingHeap(): - root(nullptr) - { - } - - // Copy constructor - template - PairingHeap::PairingHeap(PairingHeap const *other): - root(nullptr) - { - for (auto const& it:other->nodes) - push(it.first, it.second->value); - } - - // Destructor - template - PairingHeap::~PairingHeap() - { - for (auto const& it: nodes) - delete it.second; - } - - // Insert an item into the heap with specified value (priority) - template - void PairingHeap::push(const TI &some_item, const TV &some_value) - { - if (nodes.find(some_item) != nodes.end()) { - throw std::invalid_argument("item already in the heap"); - } - PairingHeapNode *p = new PairingHeapNode(some_item, some_value); - nodes[some_item] = p; - root = root == nullptr ? p : _merge(root, p); - } - - // Return the top pair (value, item) of the heap - template - inline std::pair PairingHeap::top() const - { - if (root == nullptr) { - throw std::domain_error("trying to access the top of an empty heap"); - } - return std::make_pair(root->item, root->value); - } - - // Return the top item of the heap - template - inline TI PairingHeap::top_item() const - { - if (root == nullptr) { - throw std::domain_error("trying to access the top of an empty heap"); - } - return root->item; - } + // Pair list of heaps and return pointer to the top of resulting heap + static PairingHeapNode *_pair(PairingHeapNode *p) { + if (p == nullptr) { + return nullptr; + } - // Return the top value of the heap - template - inline TV PairingHeap::top_value() const - { - if (root == nullptr) { - throw std::domain_error("trying to access the top of an empty heap"); - } - return root->value; - } + /* + * Move toward the end of the list, counting elements along the way. + * This is done in order to: + * - know whether the list has odd or even number of nodes + * - speed up going-back through the list + */ + size_t children = 1; + PairingHeapNode *it = p; + while (it->next != nullptr) { + it = it->next; + children++; + } - // Remove the top element from the heap - template - void PairingHeap::pop() - { - if (root != nullptr) { - PairingHeapNode *p = root->child; - nodes.erase(root->item); - delete root; - root = _pair(p); - } - } + PairingHeapNode *result; + + if (children % 2 == 1) { + PairingHeapNode *a = it; + it = it->prev; + a->prev = a->next = nullptr; + result = a; + } else { + PairingHeapNode *a = it; + PairingHeapNode *b = it->prev; + it = it->prev->prev; + a->prev = a->next = b->prev = b->next = nullptr; + result = _merge(a, b); + } - // Decrease the value of specified item - // If the item is not in the heap, push it - template - void PairingHeap::decrease(const TI &some_item, const TV &new_value) - { - if(contains(some_item)) { - PairingHeapNode *p = nodes[some_item]; - if (p->value <= new_value) { - throw std::invalid_argument("the new value must be less than the current value"); + for (size_t i = 0; i < (children - 1) / 2; i++) { + PairingHeapNode *a = it; + PairingHeapNode *b = it->prev; + it = it->prev->prev; + a->prev = a->next = b->prev = b->next = nullptr; + result = _merge(_merge(a, b), result); } - p->value = new_value; - if(p->prev != nullptr) { - _unlink(p); - root = _merge(root, p); - } - } else { - push(some_item, new_value); - } - } - // Return the value associated with the item - template - inline TV PairingHeap::value(const TI &some_item) - { - if (nodes.find(some_item) == nodes.end()) { - throw std::invalid_argument("the specified item is not in the heap"); - } - return nodes[some_item]->value; - } + return result; + } // end _pair - // Pair list of heaps and return pointer to the top of resulting heap - template - inline PairingHeapNode *PairingHeap::_pair(PairingHeapNode *p) - { - if(p == nullptr) { - return nullptr; - } - - /* - * Move toward the end of the list, counting elements along the way. - * This is done in order to: - * - know whether the list has odd or even number of nodes - * - speed up going-back through the list - */ - size_t children = 1; - PairingHeapNode *it = p; - while(it->next != nullptr) { - it = it->next; - children++; - } - PairingHeapNode *result; - - if(children % 2 == 1) { - PairingHeapNode *a = it; - it = it->prev; - a->prev = a->next = nullptr; - result = a; - } else { - PairingHeapNode *a = it; - PairingHeapNode *b = it->prev; - it = it->prev->prev; - a->prev = a->next = b->prev = b->next = nullptr; - result = _merge(a, b); - } + // Merge 2 heaps and return pointer to the top of resulting heap + static PairingHeapNode *_merge(PairingHeapNode *a, + PairingHeapNode *b) { + if (*a <= *b) { // Use comparison method of PairingHeapNode + _link(a, b); + return a; + } else { + _link(b, a); + return b; + } + } // end _merge - for(size_t i = 0; i < (children - 1) / 2; i++) { - PairingHeapNode *a = it; - PairingHeapNode *b = it->prev; - it = it->prev->prev; - a->prev = a->next = b->prev = b->next = nullptr; - result = _merge(_merge(a, b), result); - } - return result; - } + // Make b a child of a + static void _link(PairingHeapNode *a, + PairingHeapNode *b) { + if (a->child != nullptr) { + b->next = a->child; + a->child->prev = b; + } + b->prev = a; + a->child = b; + } // end _link - // Merge 2 heaps and return pointer to the top of resulting heap - template - inline PairingHeapNode *PairingHeap::_merge(PairingHeapNode *a, PairingHeapNode *b) - { - if(*a <= *b) { // Use comparison method of PairingHeapNode - _link(a, b); - return a; - } else { - _link(b, a); - return b; - } - } - // Make b a child of a - template - inline void PairingHeap::_link(PairingHeapNode *a, PairingHeapNode *b) - { - if(a->child != nullptr) { - b->next = a->child; - a->child->prev = b; - } - b->prev = a; - a->child = b; - } + // Remove p from its parent children list + static void _unlink(PairingHeapNode *p) { + if (p->prev->child == p) { + p->prev->child = p->next; + } else { + p->prev->next = p->next; + } + if (p->next != nullptr) { + p->next->prev = p->prev; + } + p->prev = nullptr; + p->next = nullptr; + } // end _unlink - // Remove p from its parent children list - template - inline void PairingHeap::_unlink(PairingHeapNode *p) - { - if(p->prev->child == p) { - p->prev->child = p->next; - } else { - p->prev->next = p->next; - } - if(p->next != nullptr) { - p->next->prev = p->prev; - } - p->prev = nullptr; - p->next = nullptr; - } + }; // end class PairingHeap } // end namespace pairing_heap From 1f5c0a5076d03a09b47be2e227284f92e086cbb4 Mon Sep 17 00:00:00 2001 From: dcoudert Date: Sat, 7 Dec 2024 11:04:19 +0100 Subject: [PATCH 12/27] #39046: fix coding style --- src/sage/data_structures/pairing_heap.pxd | 1 - src/sage/data_structures/pairing_heap.pyx | 24 +++++++++++------------ 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/sage/data_structures/pairing_heap.pxd b/src/sage/data_structures/pairing_heap.pxd index 6e8a20cc3c3..2764db14139 100644 --- a/src/sage/data_structures/pairing_heap.pxd +++ b/src/sage/data_structures/pairing_heap.pxd @@ -19,7 +19,6 @@ cdef extern from "./pairing_heap.h" namespace "pairing_heap": PairingHeap() except + PairingHeap(PairingHeap[TypeOfItem, TypeOfValue]) except + bint empty() - void reset() void push(TypeOfItem, TypeOfValue) except + pair[TypeOfItem, TypeOfValue] top() except + TypeOfItem top_item() except + diff --git a/src/sage/data_structures/pairing_heap.pyx b/src/sage/data_structures/pairing_heap.pyx index 0364b049b78..6cd52430f01 100644 --- a/src/sage/data_structures/pairing_heap.pyx +++ b/src/sage/data_structures/pairing_heap.pyx @@ -1156,21 +1156,21 @@ def _test_PairingHeap_from_C(n=100): try: _ = HH.top() print("something goes wrong, the error has not been raised") - except ValueError, msg: + except ValueError as msg: # The error has been properly handled pass try: _ = HH.top_item() print("something goes wrong, the error has not been raised") - except ValueError, msg: + except ValueError as msg: # The error has been properly handled pass try: _ = HH.top_value() print("something goes wrong, the error has not been raised") - except ValueError, msg: + except ValueError as msg: # The error has been properly handled pass @@ -1178,7 +1178,7 @@ def _test_PairingHeap_from_C(n=100): try: _ = HH.value(123) print("something goes wrong, the error has not been raised") - except ValueError, msg: + except ValueError as msg: # The error has been properly handled pass @@ -1263,21 +1263,21 @@ def _test_PairingHeap_of_n_integers(n=100): try: _ = P.top() print("something goes wrong, the error has not been raised") - except ValueError, msg: + except ValueError as msg: # The error has been properly handled pass try: _ = P.top_item() print("something goes wrong, the error has not been raised") - except ValueError, msg: + except ValueError as msg: # The error has been properly handled pass try: _ = P.top_value() print("something goes wrong, the error has not been raised") - except ValueError, msg: + except ValueError as msg: # The error has been properly handled pass @@ -1285,7 +1285,7 @@ def _test_PairingHeap_of_n_integers(n=100): try: _ = P.value(123) print("something goes wrong, the error has not been raised") - except ValueError, msg: + except ValueError as msg: # The error has been properly handled pass @@ -1369,21 +1369,21 @@ def _test_PairingHeap_of_n_hashables(n=100): try: _ = P.top() print("something goes wrong, the error has not been raised") - except ValueError, msg: + except ValueError as msg: # The error has been properly handled pass try: _ = P.top_item() print("something goes wrong, the error has not been raised") - except ValueError, msg: + except ValueError as msg: # The error has been properly handled pass try: _ = P.top_value() print("something goes wrong, the error has not been raised") - except ValueError, msg: + except ValueError as msg: # The error has been properly handled pass @@ -1391,7 +1391,7 @@ def _test_PairingHeap_of_n_hashables(n=100): try: _ = P.value(123) print("something goes wrong, the error has not been raised") - except ValueError, msg: + except ValueError as msg: # The error has been properly handled pass From e6f609555e1c8ec79f1f297cb73cc3ff43fd25ef Mon Sep 17 00:00:00 2001 From: dcoudert Date: Sat, 7 Dec 2024 11:23:36 +0100 Subject: [PATCH 13/27] #39046: mode method top_value to parent class --- src/sage/data_structures/pairing_heap.pxd | 3 +- src/sage/data_structures/pairing_heap.pyx | 72 ++++++++--------------- 2 files changed, 25 insertions(+), 50 deletions(-) diff --git a/src/sage/data_structures/pairing_heap.pxd b/src/sage/data_structures/pairing_heap.pxd index 2764db14139..9fc9befcbd3 100644 --- a/src/sage/data_structures/pairing_heap.pxd +++ b/src/sage/data_structures/pairing_heap.pxd @@ -57,13 +57,13 @@ cdef class PairingHeap_class: cdef size_t number_of_items # number of active items cpdef bint empty(self) noexcept cpdef bint full(self) noexcept + cpdef object top_value(self) except * cdef class PairingHeap_of_n_integers(PairingHeap_class): cpdef void push(self, size_t item, object value) except * cpdef tuple top(self) except * cpdef size_t top_item(self) except * - cpdef object top_value(self) except * cpdef void pop(self) noexcept cpdef void decrease(self, size_t item, object new_value) except * cpdef object value(self, size_t item) except * @@ -75,7 +75,6 @@ cdef class PairingHeap_of_n_hashables(PairingHeap_class): cpdef void push(self, object item, object value) except * cpdef tuple top(self) except * cpdef object top_item(self) except * - cpdef object top_value(self) except * cpdef void pop(self) noexcept cpdef void decrease(self, object item, object new_value) except * cpdef object value(self, object item) except * diff --git a/src/sage/data_structures/pairing_heap.pyx b/src/sage/data_structures/pairing_heap.pyx index 6cd52430f01..7dbb4358748 100644 --- a/src/sage/data_structures/pairing_heap.pyx +++ b/src/sage/data_structures/pairing_heap.pyx @@ -326,6 +326,30 @@ cdef class PairingHeap_class: size = __len__ + cpdef object top_value(self) except *: + r""" + Return the value of the top item of the heap. + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_integers + sage: P = PairingHeap_of_n_integers(5) + sage: P.push(1, 2) + sage: P.top() + (1, 2) + sage: P.top_value() + 2 + + sage: P = PairingHeap_of_n_integers(3) + sage: P.top_value() + Traceback (most recent call last): + ... + ValueError: trying to access the top of an empty heap + """ + if not self: + raise ValueError("trying to access the top of an empty heap") + return self.root.value + # ============================================================================== # Class PairingHeap_of_n_integers @@ -512,30 +536,6 @@ cdef class PairingHeap_of_n_integers(PairingHeap_class): raise ValueError("trying to access the top of an empty heap") return self.root - self.nodes - cpdef object top_value(self) except *: - r""" - Return the value of the top item of the heap. - - EXAMPLES:: - - sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_integers - sage: P = PairingHeap_of_n_integers(5) - sage: P.push(1, 2) - sage: P.top() - (1, 2) - sage: P.top_value() - 2 - - sage: P = PairingHeap_of_n_integers(3) - sage: P.top_value() - Traceback (most recent call last): - ... - ValueError: trying to access the top of an empty heap - """ - if not self: - raise ValueError("trying to access the top of an empty heap") - return self.root.value - cpdef void pop(self) noexcept: r""" Remove the top item from the heap. @@ -886,30 +886,6 @@ cdef class PairingHeap_of_n_hashables(PairingHeap_class): cdef size_t idx = self.root - self.nodes return self._int_to_item[idx] - cpdef object top_value(self) except *: - r""" - Return the value of the top item of the heap. - - EXAMPLES:: - - sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_hashables - sage: P = PairingHeap_of_n_hashables(5) - sage: P.push(1, 2) - sage: P.top() - (1, 2) - sage: P.top_value() - 2 - - sage: P = PairingHeap_of_n_hashables(3) - sage: P.top_value() - Traceback (most recent call last): - ... - ValueError: trying to access the top of an empty heap - """ - if not self: - raise ValueError("trying to access the top of an empty heap") - return self.root.value - cpdef void pop(self) noexcept: r""" Remove the top item from the heap. From a229754f372d0479d55a0225c8c8d3953ca75caa Mon Sep 17 00:00:00 2001 From: dcoudert Date: Sat, 7 Dec 2024 11:46:26 +0100 Subject: [PATCH 14/27] #39046: avoid storing the name of the class --- src/sage/data_structures/pairing_heap.pxd | 1 - src/sage/data_structures/pairing_heap.pyx | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/sage/data_structures/pairing_heap.pxd b/src/sage/data_structures/pairing_heap.pxd index 9fc9befcbd3..0ca3b428856 100644 --- a/src/sage/data_structures/pairing_heap.pxd +++ b/src/sage/data_structures/pairing_heap.pxd @@ -49,7 +49,6 @@ cdef _unlink(PairingHeapNode * p) except * cdef class PairingHeap_class: - cdef str name # name of the data structure cdef size_t n # maximum number of items cdef PairingHeapNode * root # pointer to the top of the heap cdef PairingHeapNode * nodes # array of size n to store items diff --git a/src/sage/data_structures/pairing_heap.pyx b/src/sage/data_structures/pairing_heap.pyx index 7dbb4358748..46fcc8772f4 100644 --- a/src/sage/data_structures/pairing_heap.pyx +++ b/src/sage/data_structures/pairing_heap.pyx @@ -257,7 +257,9 @@ cdef class PairingHeap_class: sage: P PairingHeap_of_n_integers: capacity 5, size 1 """ - return f"{self.name}: capacity {self.n}, size {len(self)}" + if isinstance(self, PairingHeap_of_n_integers): + return f"PairingHeap_of_n_integers: capacity {self.n}, size {len(self)}" + return f"PairingHeap_of_n_hashables: capacity {self.n}, size {len(self)}" def __bool__(self): r""" @@ -428,7 +430,6 @@ cdef class PairingHeap_of_n_integers(PairingHeap_class): """ if not n: raise ValueError("the capacity of the heap must be strictly positive") - self.name = "PairingHeap_of_n_integers" self.n = n self.root = NULL self.nodes = check_allocarray(n, sizeof(PairingHeapNode)) @@ -770,7 +771,6 @@ cdef class PairingHeap_of_n_hashables(PairingHeap_class): """ if not n: raise ValueError("the capacity of the heap must be strictly positive") - self.name = "PairingHeap_of_n_hashables" self.n = n self.root = NULL self.nodes = check_allocarray(n, sizeof(PairingHeapNode)) From 7627405a8d5049af3264dc45e6dfd2bb097f2135 Mon Sep 17 00:00:00 2001 From: dcoudert Date: Sat, 7 Dec 2024 11:51:53 +0100 Subject: [PATCH 15/27] #39046: avoid calling bitset_clear --- src/sage/data_structures/pairing_heap.pyx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/sage/data_structures/pairing_heap.pyx b/src/sage/data_structures/pairing_heap.pyx index 46fcc8772f4..e5c6ea85c5d 100644 --- a/src/sage/data_structures/pairing_heap.pyx +++ b/src/sage/data_structures/pairing_heap.pyx @@ -103,8 +103,8 @@ from cpython.ref cimport PyObject, Py_INCREF, Py_XDECREF from cysignals.signals cimport sig_on, sig_off, sig_check from cysignals.memory cimport check_allocarray, sig_free from sage.data_structures.bitset_base cimport (bitset_init, bitset_free, - bitset_clear, bitset_add, - bitset_remove, bitset_in, + bitset_add, bitset_remove, + bitset_in, bitset_first_in_complement) from sage.misc.prandom import shuffle @@ -434,7 +434,6 @@ cdef class PairingHeap_of_n_integers(PairingHeap_class): self.root = NULL self.nodes = check_allocarray(n, sizeof(PairingHeapNode)) bitset_init(self.active, n) - bitset_clear(self.active) self.number_of_items = 0 cpdef void push(self, size_t item, object value) except *: @@ -775,7 +774,6 @@ cdef class PairingHeap_of_n_hashables(PairingHeap_class): self.root = NULL self.nodes = check_allocarray(n, sizeof(PairingHeapNode)) bitset_init(self.active, n) - bitset_clear(self.active) self.number_of_items = 0 self._int_to_item = [None] * n self._item_to_int = dict() From 4f08a264296d07a2c869c6b358ec750db83790c8 Mon Sep 17 00:00:00 2001 From: dcoudert Date: Sat, 7 Dec 2024 12:11:25 +0100 Subject: [PATCH 16/27] #39046: avoid th use of new for PairingHeap in _test_PairingHeap_from_C --- src/sage/data_structures/pairing_heap.pyx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/sage/data_structures/pairing_heap.pyx b/src/sage/data_structures/pairing_heap.pyx index e5c6ea85c5d..641546d45a4 100644 --- a/src/sage/data_structures/pairing_heap.pyx +++ b/src/sage/data_structures/pairing_heap.pyx @@ -1044,7 +1044,7 @@ def _test_PairingHeap_from_C(n=100): """ from sage.misc.prandom import randint, shuffle sig_on() - cdef PairingHeap[size_t, size_t] * PH = new PairingHeap[size_t, size_t]() + cdef PairingHeap[size_t, size_t] PH = PairingHeap[size_t, size_t]() sig_off() # Initialize a list of tuples (value, item) randomly ordered @@ -1101,14 +1101,10 @@ def _test_PairingHeap_from_C(n=100): if L != sorted(Lref): raise ValueError('the order is not good') - sig_on() - sig_free(PH) - sig_off() - # Different cost function from sage.functions.trig import sin, cos sig_on() - cdef PairingHeap[size_t, pair[size_t, size_t]] * HH = new PairingHeap[size_t, pair[size_t, size_t]]() + cdef PairingHeap[size_t, pair[size_t, size_t]] HH = PairingHeap[size_t, pair[size_t, size_t]]() sig_off() for i in range(n): From eaa5fe5996311bfab60f924ed0f227f0f102847f Mon Sep 17 00:00:00 2001 From: dcoudert Date: Sat, 7 Dec 2024 12:26:43 +0100 Subject: [PATCH 17/27] #39046: mode declaration of top and pop to parent class --- src/sage/data_structures/pairing_heap.pxd | 6 ++-- src/sage/data_structures/pairing_heap.pyx | 42 +++++++++++++++++++++++ 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/sage/data_structures/pairing_heap.pxd b/src/sage/data_structures/pairing_heap.pxd index 0ca3b428856..bc447e1dc79 100644 --- a/src/sage/data_structures/pairing_heap.pxd +++ b/src/sage/data_structures/pairing_heap.pxd @@ -56,14 +56,14 @@ cdef class PairingHeap_class: cdef size_t number_of_items # number of active items cpdef bint empty(self) noexcept cpdef bint full(self) noexcept + cpdef tuple top(self) except * cpdef object top_value(self) except * + cpdef void pop(self) noexcept cdef class PairingHeap_of_n_integers(PairingHeap_class): cpdef void push(self, size_t item, object value) except * - cpdef tuple top(self) except * cpdef size_t top_item(self) except * - cpdef void pop(self) noexcept cpdef void decrease(self, size_t item, object new_value) except * cpdef object value(self, size_t item) except * @@ -72,8 +72,6 @@ cdef class PairingHeap_of_n_hashables(PairingHeap_class): cdef list _int_to_item # mapping from integers to items cdef dict _item_to_int # mapping from items to integers cpdef void push(self, object item, object value) except * - cpdef tuple top(self) except * cpdef object top_item(self) except * - cpdef void pop(self) noexcept cpdef void decrease(self, object item, object new_value) except * cpdef object value(self, object item) except * diff --git a/src/sage/data_structures/pairing_heap.pyx b/src/sage/data_structures/pairing_heap.pyx index 641546d45a4..bd8ca1418f8 100644 --- a/src/sage/data_structures/pairing_heap.pyx +++ b/src/sage/data_structures/pairing_heap.pyx @@ -328,6 +328,29 @@ cdef class PairingHeap_class: size = __len__ + cpdef tuple top(self) except *: + r""" + Return the top pair (item, value) of the heap. + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_integers + sage: P = PairingHeap_of_n_integers(5) + sage: P.push(1, 2) + sage: P.top() + (1, 2) + sage: P.push(3, 1) + sage: P.top() + (3, 1) + + sage: P = PairingHeap_of_n_integers(3) + sage: P.top() + Traceback (most recent call last): + ... + ValueError: trying to access the top of an empty heap + """ + raise NotImplementedError() + cpdef object top_value(self) except *: r""" Return the value of the top item of the heap. @@ -352,6 +375,25 @@ cdef class PairingHeap_class: raise ValueError("trying to access the top of an empty heap") return self.root.value + cpdef void pop(self) noexcept: + r""" + Remove the top item from the heap. + + If the heap is already empty, we do nothing. + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_integers + sage: P = PairingHeap_of_n_integers(5); P + PairingHeap_of_n_integers: capacity 5, size 0 + sage: P.push(1, 2); P + PairingHeap_of_n_integers: capacity 5, size 1 + sage: P.pop(); P + PairingHeap_of_n_integers: capacity 5, size 0 + sage: P.pop(); P + PairingHeap_of_n_integers: capacity 5, size 0 + """ + raise NotImplementedError() # ============================================================================== # Class PairingHeap_of_n_integers From 982e53e49112c7ee88ddad31c0fac1aa81029782 Mon Sep 17 00:00:00 2001 From: dcoudert Date: Sat, 7 Dec 2024 13:06:00 +0100 Subject: [PATCH 18/27] #39046: minor changes in pairing_heap.h --- src/sage/data_structures/pairing_heap.h | 187 +++++++++++++----------- 1 file changed, 101 insertions(+), 86 deletions(-) diff --git a/src/sage/data_structures/pairing_heap.h b/src/sage/data_structures/pairing_heap.h index f14cbdd81e6..966cec3e71c 100644 --- a/src/sage/data_structures/pairing_heap.h +++ b/src/sage/data_structures/pairing_heap.h @@ -88,6 +88,107 @@ namespace pairing_heap { }; // end struct PairingHeapNode + // Remove p from its parent children list + template< + typename TI, // type of items stored in the node + typename TV // type of values associated with the stored item + > + static void _unlink(PairingHeapNode *p) { + if (p->prev->child == p) { + p->prev->child = p->next; + } else { + p->prev->next = p->next; + } + if (p->next != nullptr) { + p->next->prev = p->prev; + } + p->prev = nullptr; + p->next = nullptr; + } // end _unlink + + // Pair list of heaps and return pointer to the top of resulting heap + template< + typename TI, // type of items stored in the node + typename TV // type of values associated with the stored item + > + static PairingHeapNode *_pair(PairingHeapNode *p) { + if (p == nullptr) { + return nullptr; + } + + /* + * Move toward the end of the list, counting elements along the way. + * This is done in order to: + * - know whether the list has odd or even number of nodes + * - speed up going-back through the list + */ + size_t children = 1; + PairingHeapNode *it = p; + while (it->next != nullptr) { + it = it->next; + children++; + } + + PairingHeapNode *result; + + if (children % 2 == 1) { + PairingHeapNode *a = it; + it = it->prev; + a->prev = a->next = nullptr; + result = a; + } else { + PairingHeapNode *a = it; + PairingHeapNode *b = it->prev; + it = it->prev->prev; + a->prev = a->next = b->prev = b->next = nullptr; + result = _merge(a, b); + } + + for (size_t i = 0; i < (children - 1) / 2; i++) { + PairingHeapNode *a = it; + PairingHeapNode *b = it->prev; + it = it->prev->prev; + a->prev = a->next = b->prev = b->next = nullptr; + result = _merge(_merge(a, b), result); + } + + return result; + } // end _pair + + + // Merge 2 heaps and return pointer to the top of resulting heap + template< + typename TI, // type of items stored in the node + typename TV // type of values associated with the stored item + > + static PairingHeapNode *_merge(PairingHeapNode *a, + PairingHeapNode *b) { + if (*a <= *b) { // Use comparison method of PairingHeapNode + _link(a, b); + return a; + } else { + _link(b, a); + return b; + } + } // end _merge + + + // Make b a child of a + template< + typename TI, // type of items stored in the node + typename TV // type of values associated with the stored item + > + static void _link(PairingHeapNode *a, + PairingHeapNode *b) { + if (a->child != nullptr) { + b->next = a->child; + a->child->prev = b; + } + b->prev = a; + a->child = b; + } // end _link + + template< typename TI, // type of items stored in the node typename TV // type of values associated with the stored item @@ -215,92 +316,6 @@ namespace pairing_heap { // Map used to access stored items std::unordered_map *> nodes; - - // Pair list of heaps and return pointer to the top of resulting heap - static PairingHeapNode *_pair(PairingHeapNode *p) { - if (p == nullptr) { - return nullptr; - } - - /* - * Move toward the end of the list, counting elements along the way. - * This is done in order to: - * - know whether the list has odd or even number of nodes - * - speed up going-back through the list - */ - size_t children = 1; - PairingHeapNode *it = p; - while (it->next != nullptr) { - it = it->next; - children++; - } - - PairingHeapNode *result; - - if (children % 2 == 1) { - PairingHeapNode *a = it; - it = it->prev; - a->prev = a->next = nullptr; - result = a; - } else { - PairingHeapNode *a = it; - PairingHeapNode *b = it->prev; - it = it->prev->prev; - a->prev = a->next = b->prev = b->next = nullptr; - result = _merge(a, b); - } - - for (size_t i = 0; i < (children - 1) / 2; i++) { - PairingHeapNode *a = it; - PairingHeapNode *b = it->prev; - it = it->prev->prev; - a->prev = a->next = b->prev = b->next = nullptr; - result = _merge(_merge(a, b), result); - } - - return result; - } // end _pair - - - // Merge 2 heaps and return pointer to the top of resulting heap - static PairingHeapNode *_merge(PairingHeapNode *a, - PairingHeapNode *b) { - if (*a <= *b) { // Use comparison method of PairingHeapNode - _link(a, b); - return a; - } else { - _link(b, a); - return b; - } - } // end _merge - - - // Make b a child of a - static void _link(PairingHeapNode *a, - PairingHeapNode *b) { - if (a->child != nullptr) { - b->next = a->child; - a->child->prev = b; - } - b->prev = a; - a->child = b; - } // end _link - - - // Remove p from its parent children list - static void _unlink(PairingHeapNode *p) { - if (p->prev->child == p) { - p->prev->child = p->next; - } else { - p->prev->next = p->next; - } - if (p->next != nullptr) { - p->next->prev = p->prev; - } - p->prev = nullptr; - p->next = nullptr; - } // end _unlink - }; // end class PairingHeap } // end namespace pairing_heap From 8aa716d51feccfabe659d6e8bd3c70e1196a3fb0 Mon Sep 17 00:00:00 2001 From: dcoudert Date: Sun, 8 Dec 2024 18:02:49 +0100 Subject: [PATCH 19/27] #39046: alternative to bitset_first_in_complement --- src/sage/data_structures/pairing_heap.pxd | 3 ++- src/sage/data_structures/pairing_heap.pyx | 7 +++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sage/data_structures/pairing_heap.pxd b/src/sage/data_structures/pairing_heap.pxd index bc447e1dc79..bbf424baad6 100644 --- a/src/sage/data_structures/pairing_heap.pxd +++ b/src/sage/data_structures/pairing_heap.pxd @@ -52,7 +52,6 @@ cdef class PairingHeap_class: cdef size_t n # maximum number of items cdef PairingHeapNode * root # pointer to the top of the heap cdef PairingHeapNode * nodes # array of size n to store items - cdef bitset_t active # bitset to identify active items cdef size_t number_of_items # number of active items cpdef bint empty(self) noexcept cpdef bint full(self) noexcept @@ -62,6 +61,7 @@ cdef class PairingHeap_class: cdef class PairingHeap_of_n_integers(PairingHeap_class): + cdef bitset_t active # bitset to identify active items cpdef void push(self, size_t item, object value) except * cpdef size_t top_item(self) except * cpdef void decrease(self, size_t item, object new_value) except * @@ -71,6 +71,7 @@ cdef class PairingHeap_of_n_integers(PairingHeap_class): cdef class PairingHeap_of_n_hashables(PairingHeap_class): cdef list _int_to_item # mapping from integers to items cdef dict _item_to_int # mapping from items to integers + cdef list free_idx # list of free indexes cpdef void push(self, object item, object value) except * cpdef object top_item(self) except * cpdef void decrease(self, object item, object new_value) except * diff --git a/src/sage/data_structures/pairing_heap.pyx b/src/sage/data_structures/pairing_heap.pyx index bd8ca1418f8..a8652101433 100644 --- a/src/sage/data_structures/pairing_heap.pyx +++ b/src/sage/data_structures/pairing_heap.pyx @@ -815,10 +815,10 @@ cdef class PairingHeap_of_n_hashables(PairingHeap_class): self.n = n self.root = NULL self.nodes = check_allocarray(n, sizeof(PairingHeapNode)) - bitset_init(self.active, n) self.number_of_items = 0 self._int_to_item = [None] * n self._item_to_int = dict() + self.free_idx = list(range(n)) cpdef void push(self, object item, object value) except *: r""" @@ -861,7 +861,7 @@ cdef class PairingHeap_of_n_hashables(PairingHeap_class): if self.full(): raise ValueError("the heap is full") - cdef size_t idx = bitset_first_in_complement(self.active) + cdef size_t idx = self.free_idx.pop() self._int_to_item[idx] = item self._item_to_int[item] = idx cdef PairingHeapNode * p = self.nodes + idx @@ -872,7 +872,6 @@ cdef class PairingHeap_of_n_hashables(PairingHeap_class): self.root = p else: self.root = _merge(self.root, p) - bitset_add(self.active, idx) self.number_of_items += 1 cpdef tuple top(self) except *: @@ -953,7 +952,7 @@ cdef class PairingHeap_of_n_hashables(PairingHeap_class): cdef object item = self.top_item() cdef size_t idx = self._item_to_int[item] Py_XDECREF(self.nodes[idx].value) - bitset_remove(self.active, idx) + self.free_idx.append(idx) del self._item_to_int[item] self.number_of_items -= 1 self.root = _pair(self.root.child) From b17bd70a999352c9358b4656711ddd891dc1be40 Mon Sep 17 00:00:00 2001 From: dcoudert Date: Sun, 8 Dec 2024 18:20:22 +0100 Subject: [PATCH 20/27] #39046: missing commit --- src/sage/data_structures/pairing_heap.pyx | 41 ++++++++++++++--------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/src/sage/data_structures/pairing_heap.pyx b/src/sage/data_structures/pairing_heap.pyx index a8652101433..0b0e2b96fb4 100644 --- a/src/sage/data_structures/pairing_heap.pyx +++ b/src/sage/data_structures/pairing_heap.pyx @@ -104,8 +104,7 @@ from cysignals.signals cimport sig_on, sig_off, sig_check from cysignals.memory cimport check_allocarray, sig_free from sage.data_structures.bitset_base cimport (bitset_init, bitset_free, bitset_add, bitset_remove, - bitset_in, - bitset_first_in_complement) + bitset_in) from sage.misc.prandom import shuffle # ============================================================================== @@ -231,19 +230,6 @@ cdef class PairingHeap_class: Common class and methods for :class:`PairingHeap_of_n_integers` and :class:`PairingHeap_of_n_hashables`. """ - def __dealloc__(self): - """ - Deallocate ``self``. - - EXAMPLES:: - - sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_integers - sage: P = PairingHeap_of_n_integers(5) - sage: del P - """ - sig_free(self.nodes) - bitset_free(self.active) - def __repr__(self): r""" Return a string representing ``self``. @@ -478,6 +464,19 @@ cdef class PairingHeap_of_n_integers(PairingHeap_class): bitset_init(self.active, n) self.number_of_items = 0 + def __dealloc__(self): + """ + Deallocate ``self``. + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_integers + sage: P = PairingHeap_of_n_integers(5) + sage: del P + """ + sig_free(self.nodes) + bitset_free(self.active) + cpdef void push(self, size_t item, object value) except *: r""" Insert an item into the heap with specified value (priority). @@ -820,6 +819,18 @@ cdef class PairingHeap_of_n_hashables(PairingHeap_class): self._item_to_int = dict() self.free_idx = list(range(n)) + def __dealloc__(self): + """ + Deallocate ``self``. + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_integers + sage: P = PairingHeap_of_n_integers(5) + sage: del P + """ + sig_free(self.nodes) + cpdef void push(self, object item, object value) except *: r""" Insert an item into the heap with specified value (priority). From 94bf13eaed58fcfbb98db034aae38c8ea5122ed8 Mon Sep 17 00:00:00 2001 From: dcoudert Date: Tue, 10 Dec 2024 19:29:11 +0100 Subject: [PATCH 21/27] #39046: all suggested improvements --- src/sage/data_structures/pairing_heap.h | 441 ++++++++++++---------- src/sage/data_structures/pairing_heap.pxd | 49 +-- src/sage/data_structures/pairing_heap.pyx | 176 ++------- 3 files changed, 295 insertions(+), 371 deletions(-) diff --git a/src/sage/data_structures/pairing_heap.h b/src/sage/data_structures/pairing_heap.h index 966cec3e71c..0f5f082480d 100644 --- a/src/sage/data_structures/pairing_heap.h +++ b/src/sage/data_structures/pairing_heap.h @@ -62,261 +62,284 @@ namespace pairing_heap { template< - typename TI, // type of items stored in the node - typename TV // type of values associated with the stored item + typename TV, // type of values + typename T // type of the child class > - struct PairingHeapNode { - TI item; // item contained in the node - TV value; // value associated with the item - - PairingHeapNode * prev; // Previous sibling of the node or parent - PairingHeapNode * next; // Next sibling of the node - PairingHeapNode * child; // First child of the node + struct PairingHeapNodeBase { + public: - explicit PairingHeapNode(const TI &some_item, const TV &some_value) - : item(some_item), value(some_value), - prev(nullptr), next(nullptr), child(nullptr) { + bool operator<=(PairingHeapNodeBase const& other) const { + return static_cast(this)->le_implem(static_cast(other)); } - bool operator<(PairingHeapNode const& other) const { - return value < other.value; - } + // Pair list of heaps and return pointer to the top of resulting heap + static T *_pair(T *p) { + if (p == nullptr) { + return nullptr; + } - bool operator<=(PairingHeapNode const& other) const { - return value <= other.value; - } - }; // end struct PairingHeapNode + /* + * Move toward the end of the list, counting elements along the way. + * This is done in order to: + * - know whether the list has odd or even number of nodes + * - speed up going-back through the list + */ + size_t children = 1; + T *it = p; + while (it->next != nullptr) { + it = it->next; + children++; + } + T *result; - // Remove p from its parent children list - template< - typename TI, // type of items stored in the node - typename TV // type of values associated with the stored item - > - static void _unlink(PairingHeapNode *p) { - if (p->prev->child == p) { - p->prev->child = p->next; - } else { - p->prev->next = p->next; - } - if (p->next != nullptr) { - p->next->prev = p->prev; - } - p->prev = nullptr; - p->next = nullptr; - } // end _unlink + if (children % 2 == 1) { + T *a = it; + it = it->prev; + a->prev = a->next = nullptr; + result = a; + } else { + T *a = it; + T *b = it->prev; + it = it->prev->prev; + a->prev = a->next = b->prev = b->next = nullptr; + result = _merge(a, b); + } - // Pair list of heaps and return pointer to the top of resulting heap - template< - typename TI, // type of items stored in the node - typename TV // type of values associated with the stored item - > - static PairingHeapNode *_pair(PairingHeapNode *p) { - if (p == nullptr) { - return nullptr; - } + for (size_t i = 0; i < (children - 1) / 2; i++) { + T *a = it; + T *b = it->prev; + it = it->prev->prev; + a->prev = a->next = b->prev = b->next = nullptr; + result = _merge(_merge(a, b), result); + } - /* - * Move toward the end of the list, counting elements along the way. - * This is done in order to: - * - know whether the list has odd or even number of nodes - * - speed up going-back through the list - */ - size_t children = 1; - PairingHeapNode *it = p; - while (it->next != nullptr) { - it = it->next; - children++; - } + return result; + } // end _pair - PairingHeapNode *result; - if (children % 2 == 1) { - PairingHeapNode *a = it; - it = it->prev; - a->prev = a->next = nullptr; - result = a; - } else { - PairingHeapNode *a = it; - PairingHeapNode *b = it->prev; - it = it->prev->prev; - a->prev = a->next = b->prev = b->next = nullptr; - result = _merge(a, b); - } + // Merge 2 heaps and return pointer to the top of resulting heap + static T *_merge(T *a, T *b) { + if (*a <= *b) { // Use comparison method of PairingHeapNodeBase + _link(a, b); + return a; + } else { + _link(b, a); + return b; + } + } // end _merge - for (size_t i = 0; i < (children - 1) / 2; i++) { - PairingHeapNode *a = it; - PairingHeapNode *b = it->prev; - it = it->prev->prev; - a->prev = a->next = b->prev = b->next = nullptr; - result = _merge(_merge(a, b), result); - } - return result; - } // end _pair + // Make b a child of a + static void _link(T *a, T *b) { + if (a->child != nullptr) { + b->next = a->child; + a->child->prev = b; + } + b->prev = a; + a->child = b; + } // end _link - // Merge 2 heaps and return pointer to the top of resulting heap - template< - typename TI, // type of items stored in the node - typename TV // type of values associated with the stored item - > - static PairingHeapNode *_merge(PairingHeapNode *a, - PairingHeapNode *b) { - if (*a <= *b) { // Use comparison method of PairingHeapNode - _link(a, b); - return a; - } else { - _link(b, a); - return b; - } - } // end _merge + // Remove p from its parent children list + static void _unlink(T *p) { + if (p->prev->child == p) { + p->prev->child = p->next; + } else { + p->prev->next = p->next; + } + if (p->next != nullptr) { + p->next->prev = p->prev; + } + p->prev = nullptr; + p->next = nullptr; + } // end _unlink - // Make b a child of a - template< - typename TI, // type of items stored in the node - typename TV // type of values associated with the stored item - > - static void _link(PairingHeapNode *a, - PairingHeapNode *b) { - if (a->child != nullptr) { - b->next = a->child; - a->child->prev = b; - } - b->prev = a; - a->child = b; - } // end _link + TV value; // value associated to the node + T * prev; // Previous sibling of the node or parent + T * next; // Next sibling of the node + T * child; // First child of the node + + protected: + // Only derived class can build a PairingHeapNodeBase + explicit PairingHeapNodeBase(const TV &some_value) + : value{some_value}, prev{nullptr}, next{nullptr}, child{nullptr} { + } + }; // end struct PairingHeapNodeBase template< typename TI, // type of items stored in the node typename TV // type of values associated with the stored item + // Assumes TV is a comparable type > - class PairingHeap - { - public: + class PairingHeapNode + : public PairingHeapNodeBase> { - // Constructor - explicit PairingHeap() - : root(nullptr) { - } + public: + PairingHeapNode(TI const& some_item, TV const& some_value) + : Base_(some_value), item(some_item) { + } - // Copy constructor - PairingHeap(PairingHeap const *other) - : root(nullptr) { - for (auto const& it: other->nodes) { - push(it.first, it.second->value); - } - } + bool le_implem(PairingHeapNode const& other) const { + return this->value <= other.value; + } - // Destructor - virtual ~PairingHeap() { - for (auto const& it: nodes) { - delete it.second; - } - } + TI item; // item contained in the node - // Return true if the heap is empty, else false - bool empty() const { - return root == nullptr; - } + private: + using Base_ = PairingHeapNodeBase>; + }; - // Return true if the heap is not empty, else false - explicit operator bool() const { - return root != nullptr; - } - // Insert an item into the heap with specified value (priority) - void push(const TI &some_item, const TV &some_value) { - if (nodes.find(some_item) != nodes.end()) { - throw std::invalid_argument("item already in the heap"); - } - PairingHeapNode *p = new PairingHeapNode(some_item, some_value); - nodes[some_item] = p; - root = root == nullptr ? p : _merge(root, p); - } + class PairingHeapNodePy + : public PairingHeapNodeBase { + public: + PairingHeapNodePy(PyObject *some_value) + : Base_(some_value) { + } - // Return the top pair (item, value) of the heap - std::pair top() const { - if (root == nullptr) { - throw std::domain_error("trying to access the top of an empty heap"); - } - return std::make_pair(root->item, root->value); - } + bool le_implem(PairingHeapNodePy const& other) const { + return PyObject_RichCompareBool(this->value, other.value, Py_LE); + } - // Return the top item of the heap - TI top_item() const { - if (root == nullptr) { - throw std::domain_error("trying to access the top of an empty heap"); - } - return root->item; - } + private: + using Base_ = PairingHeapNodeBase; + }; - // Return the top value of the heap - TV top_value() const { - if (root == nullptr) { - throw std::domain_error("trying to access the top of an empty heap"); - } - return root->value; - } - // Remove the top element from the heap. Do nothing if empty - void pop() { - if (root != nullptr) { - PairingHeapNode *p = root->child; - nodes.erase(root->item); - delete root; - root = _pair(p); - } + + template< + typename TI, // type of items stored in the node + typename TV // type of values associated with the stored item + // Assume TV is a comparable type + > + class PairingHeap + { + public: + using HeapNodeType = PairingHeapNode; + + // Constructor + explicit PairingHeap() + : root(nullptr) { + } + + // Copy constructor + PairingHeap(PairingHeap const *other) + : root(nullptr) { + for (auto const& it: other->nodes) { + push(it.first, it.second->value); } + } - // Decrease the value of specified item - // If the item is not in the heap, push it - void decrease(const TI &some_item, const TV &new_value) { - if (contains(some_item)) { - PairingHeapNode *p = nodes[some_item]; - if (p->value <= new_value) { - throw std::invalid_argument("the new value must be less than the current value"); - } - p->value = new_value; - if (p->prev != nullptr) { - _unlink(p); - root = _merge(root, p); - } - } else { - push(some_item, new_value); - } + // Destructor + virtual ~PairingHeap() { + for (auto const& it: nodes) { + delete it.second; + } + } + + // Return true if the heap is empty, else false + bool empty() const { + return root == nullptr; + } + + // Return true if the heap is not empty, else false + explicit operator bool() const { + return root != nullptr; + } + + // Insert an item into the heap with specified value (priority) + void push(const TI &some_item, const TV &some_value) { + if (nodes.find(some_item) != nodes.end()) { + throw std::invalid_argument("item already in the heap"); + } + PairingHeapNode *p = new PairingHeapNode(some_item, some_value); + nodes[some_item] = p; + root = root == nullptr ? p : HeapNodeType::_merge(root, p); + } + + // Return the top pair (item, value) of the heap + std::pair top() const { + if (root == nullptr) { + throw std::domain_error("trying to access the top of an empty heap"); } + return std::make_pair(root->item, root->value); + } - // Check if specified item is in the heap - bool contains(TI const& some_item) const { - return nodes.find(some_item) != nodes.end(); + // Return the top item of the heap + TI top_item() const { + if (root == nullptr) { + throw std::domain_error("trying to access the top of an empty heap"); } + return root->item; + } - // Return the value associated with the item - TV value(const TI &some_item) const { - auto it = nodes.find(some_item); - if (it == nodes.end()) { - throw std::invalid_argument("the specified item is not in the heap"); + // Return the top value of the heap + TV top_value() const { + if (root == nullptr) { + throw std::domain_error("trying to access the top of an empty heap"); + } + return root->value; + } + + // Remove the top element from the heap. Do nothing if empty + void pop() { + if (root != nullptr) { + PairingHeapNode *p = root->child; + nodes.erase(root->item); + delete root; + root = HeapNodeType::_pair(p); + } + } + + // Decrease the value of specified item + // If the item is not in the heap, push it + void decrease(const TI &some_item, const TV &new_value) { + if (contains(some_item)) { + PairingHeapNode *p = nodes[some_item]; + if (p->value <= new_value) { + throw std::invalid_argument("the new value must be less than the current value"); } - return it->second->value; + p->value = new_value; + if (p->prev != nullptr) { + HeapNodeType::_unlink(p); + root = HeapNodeType::_merge(root, p); + } + } else { + push(some_item, new_value); } - - // Return the number of items in the heap - size_t size() const { - return nodes.size(); + } + + // Check if specified item is in the heap + bool contains(TI const& some_item) const { + return nodes.find(some_item) != nodes.end(); + } + + // Return the value associated with the item + TV value(const TI &some_item) const { + auto it = nodes.find(some_item); + if (it == nodes.end()) { + throw std::invalid_argument("the specified item is not in the heap"); } + return it->second->value; + } + + // Return the number of items in the heap + size_t size() const { + return nodes.size(); + } - private: + private: - // Pointer to the top of the heap - PairingHeapNode *root; + // Pointer to the top of the heap + PairingHeapNode *root; - // Map used to access stored items - std::unordered_map *> nodes; + // Map used to access stored items + std::unordered_map *> nodes; - }; // end class PairingHeap + }; // end class PairingHeap } // end namespace pairing_heap diff --git a/src/sage/data_structures/pairing_heap.pxd b/src/sage/data_structures/pairing_heap.pxd index bbf424baad6..06e70ad2c0f 100644 --- a/src/sage/data_structures/pairing_heap.pxd +++ b/src/sage/data_structures/pairing_heap.pxd @@ -13,6 +13,7 @@ # ============================================================================== from libcpp.pair cimport pair +from cpython cimport PyObject cdef extern from "./pairing_heap.h" namespace "pairing_heap": cdef cppclass PairingHeap[TypeOfItem, TypeOfValue]: @@ -28,35 +29,37 @@ cdef extern from "./pairing_heap.h" namespace "pairing_heap": bint contains(TypeOfItem) TypeOfValue value(TypeOfItem) except + + cdef cppclass PairingHeapNodePy: + PyObject * value # value associated with the item + PairingHeapNodePy * prev # Previous sibling of the node or parent + PairingHeapNodePy * next # Next sibling of the node + PairingHeapNodePy * child # First child of the node + + @staticmethod + PairingHeapNodePy * _merge(PairingHeapNodePy * a, PairingHeapNodePy * b) except + + @staticmethod + PairingHeapNodePy * _pair(PairingHeapNodePy * p) except + + @staticmethod + void _link(PairingHeapNodePy * a, PairingHeapNodePy * b) + @staticmethod + void _unlink(PairingHeapNodePy * p) + + # ============================================================================== # Pairing heap data structure with fixed capacity n # ============================================================================== from sage.data_structures.bitset_base cimport bitset_t -ctypedef struct PairingHeapNode: - void * value # value associated with the item - PairingHeapNode * prev # Previous sibling of the node or parent - PairingHeapNode * succ # Next sibling of the node - PairingHeapNode * child # First child of the node - - -cdef bint _compare(PairingHeapNode * a, PairingHeapNode * b) except * -cdef PairingHeapNode * _pair(PairingHeapNode * p) except * -cdef PairingHeapNode * _merge(PairingHeapNode * a, PairingHeapNode * b) except * -cdef _link(PairingHeapNode * a, PairingHeapNode * b) except * -cdef _unlink(PairingHeapNode * p) except * - - cdef class PairingHeap_class: - cdef size_t n # maximum number of items - cdef PairingHeapNode * root # pointer to the top of the heap - cdef PairingHeapNode * nodes # array of size n to store items - cdef size_t number_of_items # number of active items + cdef size_t n # maximum number of items + cdef PairingHeapNodePy * root # pointer to the top of the heap + cdef PairingHeapNodePy * nodes # array of size n to store items + cdef size_t number_of_items # number of active items cpdef bint empty(self) noexcept cpdef bint full(self) noexcept - cpdef tuple top(self) except * - cpdef object top_value(self) except * + cpdef tuple top(self) + cpdef object top_value(self) cpdef void pop(self) noexcept @@ -65,7 +68,7 @@ cdef class PairingHeap_of_n_integers(PairingHeap_class): cpdef void push(self, size_t item, object value) except * cpdef size_t top_item(self) except * cpdef void decrease(self, size_t item, object new_value) except * - cpdef object value(self, size_t item) except * + cpdef object value(self, size_t item) cdef class PairingHeap_of_n_hashables(PairingHeap_class): @@ -73,6 +76,6 @@ cdef class PairingHeap_of_n_hashables(PairingHeap_class): cdef dict _item_to_int # mapping from items to integers cdef list free_idx # list of free indexes cpdef void push(self, object item, object value) except * - cpdef object top_item(self) except * + cpdef object top_item(self) cpdef void decrease(self, object item, object new_value) except * - cpdef object value(self, object item) except * + cpdef object value(self, object item) diff --git a/src/sage/data_structures/pairing_heap.pyx b/src/sage/data_structures/pairing_heap.pyx index 0b0e2b96fb4..e182b7a23c2 100644 --- a/src/sage/data_structures/pairing_heap.pyx +++ b/src/sage/data_structures/pairing_heap.pyx @@ -107,119 +107,6 @@ from sage.data_structures.bitset_base cimport (bitset_init, bitset_free, bitset_in) from sage.misc.prandom import shuffle -# ============================================================================== -# Methods for PairingHeapNode -# ============================================================================== - -cdef inline bint _compare(PairingHeapNode * a, PairingHeapNode * b) except *: - r""" - Check whether ``a.value <= b.value``. - - TESTS:: - - sage: from sage.data_structures.pairing_heap import _test_PairingHeap_of_n_integers - sage: _test_PairingHeap_of_n_integers(5) - """ - return a.value <= b.value - - -cdef inline PairingHeapNode * _pair(PairingHeapNode * p) except *: - r""" - Pair a list of heaps and return the pointer to the top of the resulting heap. - - TESTS:: - - sage: from sage.data_structures.pairing_heap import _test_PairingHeap_of_n_integers - sage: _test_PairingHeap_of_n_integers(5) - """ - if p == NULL: - return NULL - - # Move toward the end of the list, counting elements along the way. - # This is done in order to: - # - know whether the list has odd or even number of nodes - # - speed up going-back through the list - cdef size_t children = 1 - cdef PairingHeapNode * it = p - while it.succ != NULL: - it = it.succ - children += 1 - - cdef PairingHeapNode * result - cdef PairingHeapNode * a - cdef PairingHeapNode * b - - if children % 2: - a = it - it = it.prev - a.prev = a.succ = NULL - result = a - else: - a = it - b = it.prev - it = it.prev.prev - a.prev = a.succ = b.prev = b.succ = NULL - result = _merge(a, b) - - for _ in range((children - 1) // 2): - a = it - b = it.prev - it = it.prev.prev - a.prev = a.succ = b.prev = b.succ = NULL - result = _merge(_merge(a, b), result) - - return result - - -cdef inline PairingHeapNode * _merge(PairingHeapNode * a, PairingHeapNode * b) except *: - r""" - Merge two heaps and return the pointer to the top of the resulting heap. - - TESTS:: - - sage: from sage.data_structures.pairing_heap import _test_PairingHeap_of_n_integers - sage: _test_PairingHeap_of_n_integers(5) - """ - if _compare(a, b): # True if a.value <= b.value - _link(a, b) - return a - _link(b, a) - return b - - -cdef inline _link(PairingHeapNode * a, PairingHeapNode * b) except *: - r""" - Make ``b`` a child of ``a``. - - TESTS:: - - sage: from sage.data_structures.pairing_heap import _test_PairingHeap_of_n_integers - sage: _test_PairingHeap_of_n_integers(5) - """ - if a.child != NULL: - b.succ = a.child - a.child.prev = b - b.prev = a - a.child = b - - -cdef inline _unlink(PairingHeapNode * p) except *: - r""" - Remove ``p`` from the list of children of its parent. - - TESTS:: - - sage: from sage.data_structures.pairing_heap import _test_PairingHeap_of_n_integers - sage: _test_PairingHeap_of_n_integers(5) - """ - if p.prev.child == p: - p.prev.child = p.succ - else: - p.prev.succ = p.succ - if p.succ != NULL: - p.succ.prev = p.prev - p.prev = p.succ = NULL - # ============================================================================== # Class PairingHeap_class @@ -314,7 +201,7 @@ cdef class PairingHeap_class: size = __len__ - cpdef tuple top(self) except *: + cpdef tuple top(self): r""" Return the top pair (item, value) of the heap. @@ -337,7 +224,7 @@ cdef class PairingHeap_class: """ raise NotImplementedError() - cpdef object top_value(self) except *: + cpdef object top_value(self): r""" Return the value of the top item of the heap. @@ -460,7 +347,7 @@ cdef class PairingHeap_of_n_integers(PairingHeap_class): raise ValueError("the capacity of the heap must be strictly positive") self.n = n self.root = NULL - self.nodes = check_allocarray(n, sizeof(PairingHeapNode)) + self.nodes = check_allocarray(n, sizeof(PairingHeapNodePy)) bitset_init(self.active, n) self.number_of_items = 0 @@ -517,18 +404,18 @@ cdef class PairingHeap_of_n_integers(PairingHeap_class): if item in self: raise ValueError(f"{item} is already in the heap") - cdef PairingHeapNode * p = self.nodes + item + cdef PairingHeapNodePy * p = self.nodes + item Py_INCREF(value) - p.value = value - p.prev = p.succ = p.child = NULL + p.value = value + p.prev = p.next = p.child = NULL if self.root == NULL: self.root = p else: - self.root = _merge(self.root, p) + self.root = PairingHeapNodePy._merge(self.root, p) bitset_add(self.active, item) self.number_of_items += 1 - cpdef tuple top(self) except *: + cpdef tuple top(self): r""" Return the top pair (item, value) of the heap. @@ -605,7 +492,7 @@ cdef class PairingHeap_of_n_integers(PairingHeap_class): Py_XDECREF(self.nodes[item].value) bitset_remove(self.active, item) self.number_of_items -= 1 - self.root = _pair(self.root.child) + self.root = PairingHeapNodePy._pair(self.root.child) cpdef void decrease(self, size_t item, object new_value) except *: r""" @@ -648,17 +535,19 @@ cdef class PairingHeap_of_n_integers(PairingHeap_class): ... ValueError: the new value must be less than the current value """ - cdef PairingHeapNode * p + if item >= self.n: + raise ValueError(f"item must be in range 0..{self.n - 1}") + cdef PairingHeapNodePy * p if bitset_in(self.active, item): p = self.nodes + item if p.value <= new_value: raise ValueError("the new value must be less than the current value") Py_XDECREF(p.value) Py_INCREF(new_value) - p.value = new_value + p.value = new_value if p.prev != NULL: - _unlink(p) - self.root = _merge(self.root, p) + PairingHeapNodePy._unlink(p) + self.root = PairingHeapNodePy._merge(self.root, p) else: self.push(item, new_value) @@ -682,11 +571,13 @@ cdef class PairingHeap_of_n_integers(PairingHeap_class): sage: 100 in P False """ + if item >= self.n: + return False return bitset_in(self.active, item) contains = __contains__ - cpdef object value(self, size_t item) except *: + cpdef object value(self, size_t item): r""" Return the value associated with the item. @@ -766,6 +657,13 @@ cdef class PairingHeap_of_n_hashables(PairingHeap_class): Traceback (most recent call last): ... ValueError: the heap is full + + sage: P = PairingHeap_of_n_hashables(10) + sage: P.push(1, 'John') + sage: P.push(4, 42) + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for >=: 'Integer Ring' and '' """ def __init__(self, size_t n): r""" @@ -813,7 +711,7 @@ cdef class PairingHeap_of_n_hashables(PairingHeap_class): raise ValueError("the capacity of the heap must be strictly positive") self.n = n self.root = NULL - self.nodes = check_allocarray(n, sizeof(PairingHeapNode)) + self.nodes = check_allocarray(n, sizeof(PairingHeapNodePy)) self.number_of_items = 0 self._int_to_item = [None] * n self._item_to_int = dict() @@ -875,17 +773,17 @@ cdef class PairingHeap_of_n_hashables(PairingHeap_class): cdef size_t idx = self.free_idx.pop() self._int_to_item[idx] = item self._item_to_int[item] = idx - cdef PairingHeapNode * p = self.nodes + idx + cdef PairingHeapNodePy * p = self.nodes + idx Py_INCREF(value) - p.value = value - p.prev = p.succ = p.child = NULL + p.value = value + p.prev = p.next = p.child = NULL if self.root == NULL: self.root = p else: - self.root = _merge(self.root, p) + self.root = PairingHeapNodePy._merge(self.root, p) self.number_of_items += 1 - cpdef tuple top(self) except *: + cpdef tuple top(self): r""" Return the top pair (item, value) of the heap. @@ -911,7 +809,7 @@ cdef class PairingHeap_of_n_hashables(PairingHeap_class): cdef size_t idx = self.root - self.nodes return self._int_to_item[idx], self.root.value - cpdef object top_item(self) except *: + cpdef object top_item(self): r""" Return the top item of the heap. @@ -966,7 +864,7 @@ cdef class PairingHeap_of_n_hashables(PairingHeap_class): self.free_idx.append(idx) del self._item_to_int[item] self.number_of_items -= 1 - self.root = _pair(self.root.child) + self.root = PairingHeapNodePy._pair(self.root.child) cpdef void decrease(self, object item, object new_value) except *: r""" @@ -1009,7 +907,7 @@ cdef class PairingHeap_of_n_hashables(PairingHeap_class): ... ValueError: the new value must be less than the current value """ - cdef PairingHeapNode * p + cdef PairingHeapNodePy * p cdef size_t idx if item in self: idx = self._item_to_int[item] @@ -1018,10 +916,10 @@ cdef class PairingHeap_of_n_hashables(PairingHeap_class): raise ValueError("the new value must be less than the current value") Py_XDECREF(p.value) Py_INCREF(new_value) - p.value = new_value + p.value = new_value if p.prev != NULL: - _unlink(p) - self.root = _merge(self.root, p) + PairingHeapNodePy._unlink(p) + self.root = PairingHeapNodePy._merge(self.root, p) else: self.push(item, new_value) From aa016b7acfaa7420f5912436adb6323772a0b372 Mon Sep 17 00:00:00 2001 From: dcoudert Date: Wed, 11 Dec 2024 13:08:50 +0100 Subject: [PATCH 22/27] #39046: simplify __repr__ --- src/sage/data_structures/pairing_heap.pyx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/sage/data_structures/pairing_heap.pyx b/src/sage/data_structures/pairing_heap.pyx index e182b7a23c2..fda1d718c2b 100644 --- a/src/sage/data_structures/pairing_heap.pyx +++ b/src/sage/data_structures/pairing_heap.pyx @@ -130,9 +130,7 @@ cdef class PairingHeap_class: sage: P PairingHeap_of_n_integers: capacity 5, size 1 """ - if isinstance(self, PairingHeap_of_n_integers): - return f"PairingHeap_of_n_integers: capacity {self.n}, size {len(self)}" - return f"PairingHeap_of_n_hashables: capacity {self.n}, size {len(self)}" + return f"{type(self).__name__}: capacity {self.n}, size {len(self)}" def __bool__(self): r""" @@ -947,7 +945,7 @@ cdef class PairingHeap_of_n_hashables(PairingHeap_class): contains = __contains__ - cpdef object value(self, object item) except *: + cpdef object value(self, object item): r""" Return the value associated with the item. From 47ec705f2f66720c517983650a5426f7f8670bf1 Mon Sep 17 00:00:00 2001 From: dcoudert Date: Wed, 11 Dec 2024 16:58:31 +0100 Subject: [PATCH 23/27] #39046: try to make meson / conda happy --- src/sage/data_structures/meson.build | 16 +++++++++++++++- src/sage/data_structures/pairing_heap.h | 1 - 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/sage/data_structures/meson.build b/src/sage/data_structures/meson.build index 124a209dbbe..16694b083da 100644 --- a/src/sage/data_structures/meson.build +++ b/src/sage/data_structures/meson.build @@ -23,7 +23,6 @@ extension_data = { 'blas_dict' : files('blas_dict.pyx'), 'bounded_integer_sequences' : files('bounded_integer_sequences.pyx'), 'list_of_pairs' : files('list_of_pairs.pyx'), - 'pairing_heap' : files('pairing_heap.pyx'), } foreach name, pyx : extension_data @@ -42,3 +41,18 @@ foreach name, pyx : extension_data ) endforeach +extension_data_cpp = { + 'pairing_heap' : files('pairing_heap.pyx'), +} + +foreach name, pyx : extension_data_cpp + py.extension_module( + name, + sources: pyx, + subdir: 'sage/data_structures/', + install: true, + override_options: ['cython_language=cpp'], + include_directories: [inc_cpython, inc_data_structures], + dependencies: [py_dep, cysignals, gmp], + ) +endforeach diff --git a/src/sage/data_structures/pairing_heap.h b/src/sage/data_structures/pairing_heap.h index 0f5f082480d..7e93c1e8d1b 100644 --- a/src/sage/data_structures/pairing_heap.h +++ b/src/sage/data_structures/pairing_heap.h @@ -54,7 +54,6 @@ #ifndef PAIRING_HEAP_H #define PAIRING_HEAP_H -#include #include #include From 6dfd7ab753702c7dd8d803592664b73d5affed38 Mon Sep 17 00:00:00 2001 From: dcoudert Date: Thu, 12 Dec 2024 14:38:25 +0100 Subject: [PATCH 24/27] #39046: suggested improvements --- src/sage/data_structures/pairing_heap.h | 3 +- src/sage/data_structures/pairing_heap.pxd | 15 ++-- src/sage/data_structures/pairing_heap.pyx | 102 ++++++++++++++-------- 3 files changed, 79 insertions(+), 41 deletions(-) diff --git a/src/sage/data_structures/pairing_heap.h b/src/sage/data_structures/pairing_heap.h index 7e93c1e8d1b..81ac55779f6 100644 --- a/src/sage/data_structures/pairing_heap.h +++ b/src/sage/data_structures/pairing_heap.h @@ -54,9 +54,8 @@ #ifndef PAIRING_HEAP_H #define PAIRING_HEAP_H -#include #include - +#include namespace pairing_heap { diff --git a/src/sage/data_structures/pairing_heap.pxd b/src/sage/data_structures/pairing_heap.pxd index 06e70ad2c0f..d749cc7ec31 100644 --- a/src/sage/data_structures/pairing_heap.pxd +++ b/src/sage/data_structures/pairing_heap.pxd @@ -8,13 +8,15 @@ # https://www.gnu.org/licenses/ # ****************************************************************************** +from cpython cimport PyObject +from libcpp.pair cimport pair +from sage.data_structures.bitset_base cimport bitset_t + + # ============================================================================== # Interface to pairing heap data structure from ./pairing_heap.h # ============================================================================== -from libcpp.pair cimport pair -from cpython cimport PyObject - cdef extern from "./pairing_heap.h" namespace "pairing_heap": cdef cppclass PairingHeap[TypeOfItem, TypeOfValue]: PairingHeap() except + @@ -37,10 +39,13 @@ cdef extern from "./pairing_heap.h" namespace "pairing_heap": @staticmethod PairingHeapNodePy * _merge(PairingHeapNodePy * a, PairingHeapNodePy * b) except + + @staticmethod PairingHeapNodePy * _pair(PairingHeapNodePy * p) except + + @staticmethod void _link(PairingHeapNodePy * a, PairingHeapNodePy * b) + @staticmethod void _unlink(PairingHeapNodePy * p) @@ -49,8 +54,6 @@ cdef extern from "./pairing_heap.h" namespace "pairing_heap": # Pairing heap data structure with fixed capacity n # ============================================================================== -from sage.data_structures.bitset_base cimport bitset_t - cdef class PairingHeap_class: cdef size_t n # maximum number of items cdef PairingHeapNodePy * root # pointer to the top of the heap @@ -58,6 +61,8 @@ cdef class PairingHeap_class: cdef size_t number_of_items # number of active items cpdef bint empty(self) noexcept cpdef bint full(self) noexcept + cpdef size_t capacity(self) noexcept + cpdef size_t size(self) noexcept cpdef tuple top(self) cpdef object top_value(self) cpdef void pop(self) noexcept diff --git a/src/sage/data_structures/pairing_heap.pyx b/src/sage/data_structures/pairing_heap.pyx index fda1d718c2b..6a6e61e1c5e 100644 --- a/src/sage/data_structures/pairing_heap.pyx +++ b/src/sage/data_structures/pairing_heap.pyx @@ -7,7 +7,7 @@ This module proposes several implementations of the pairing heap data structure min-heap data structure. - :class:`PairingHeap_of_n_integers`: a pairing heap data structure with fixed - capacity `n`. Its items are integers in the range `0..n-1`. Values can be of + capacity `n`. Its items are integers in the range `[0, n-1]`. Values can be of any type equipped with a comparison method (``<=``). - :class:`PairingHeap_of_n_hashables`: a pairing heap data structure with fixed @@ -23,7 +23,7 @@ min-heap data structure. EXAMPLES: -Pairing heap of `n` integers in the range `0..n-1`:: +Pairing heap of `n` integers in the range `[0, n-1]`:: sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_integers sage: P = PairingHeap_of_n_integers(10); P @@ -81,11 +81,6 @@ Pairing heap of `n` hashables:: (Graph on 2 vertices, (1, 'z')) (2, (2, 'a')) (('a', 1), (2, 'b')) - -AUTHORS: - -- David Coudert (2024) - Initial version. - """ # ****************************************************************************** # Copyright (C) 2024 David Coudert @@ -117,6 +112,7 @@ cdef class PairingHeap_class: Common class and methods for :class:`PairingHeap_of_n_integers` and :class:`PairingHeap_of_n_hashables`. """ + def __repr__(self): r""" Return a string representing ``self``. @@ -150,7 +146,7 @@ cdef class PairingHeap_class: cpdef bint empty(self) noexcept: r""" - Check whether the heap is empty or not. + Check whether the heap is empty. EXAMPLES:: @@ -166,7 +162,7 @@ cdef class PairingHeap_class: cpdef bint full(self) noexcept: r""" - Check whether the heap is full or not. + Check whether the heap is full. EXAMPLES:: @@ -181,6 +177,43 @@ cdef class PairingHeap_class: """ return self.n == self.number_of_items + cpdef size_t capacity(self) noexcept: + r""" + Return the maximum capacity of the heap. + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_integers + sage: P = PairingHeap_of_n_integers(5) + sage: P.capacity() + 5 + sage: P.push(1, 2) + sage: P.capacity() + 5 + """ + return self.n + + cpdef size_t size(self) noexcept: + r""" + Return the number of items in the heap. + + EXAMPLES:: + + sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_integers + sage: P = PairingHeap_of_n_integers(5) + sage: P.size() + 0 + sage: P.push(1, 2) + sage: P.size() + 1 + + One may also use Python's `__len__`:: + + sage: len(P) + 1 + """ + return self.number_of_items + def __len__(self): r""" Return the number of items in the heap. @@ -197,8 +230,6 @@ cdef class PairingHeap_class: """ return self.number_of_items - size = __len__ - cpdef tuple top(self): r""" Return the top pair (item, value) of the heap. @@ -266,13 +297,14 @@ cdef class PairingHeap_class: """ raise NotImplementedError() + # ============================================================================== # Class PairingHeap_of_n_integers # ============================================================================== cdef class PairingHeap_of_n_integers(PairingHeap_class): r""" - Pairing Heap for items in range [0..n - 1]. + Pairing Heap for items in range `[0, n - 1]`. EXAMPLES:: @@ -305,12 +337,13 @@ cdef class PairingHeap_of_n_integers(PairingHeap_class): sage: P.push(11, 3) Traceback (most recent call last): ... - ValueError: item must be in range 0..4 + ValueError: item must be in range [0, 4] """ + def __init__(self, size_t n): r""" Construct the ``PairingHeap_of_n_integers`` where items are integers - from ``0`` to ``n-1``. + from `0` to `n-1`. INPUT: @@ -321,24 +354,21 @@ cdef class PairingHeap_of_n_integers(PairingHeap_class): sage: from sage.data_structures.pairing_heap import PairingHeap_of_n_integers sage: P = PairingHeap_of_n_integers(5); P PairingHeap_of_n_integers: capacity 5, size 0 - sage: P.push(1, 2) - sage: P + sage: P.push(1, 2); P PairingHeap_of_n_integers: capacity 5, size 1 - sage: P.push(2, 3) - sage: P + sage: P.push(2, 3); P PairingHeap_of_n_integers: capacity 5, size 2 - sage: P.pop() - sage: P + sage: P.pop(); P PairingHeap_of_n_integers: capacity 5, size 1 sage: P.push(10, 1) Traceback (most recent call last): ... - ValueError: item must be in range 0..4 - sage: P = PairingHeap_of_n_integers(0) + ValueError: item must be in range [0, 4] + sage: PairingHeap_of_n_integers(0) Traceback (most recent call last): ... ValueError: the capacity of the heap must be strictly positive - sage: P = PairingHeap_of_n_integers(1); P + sage: PairingHeap_of_n_integers(1) PairingHeap_of_n_integers: capacity 1, size 0 """ if not n: @@ -368,7 +398,7 @@ cdef class PairingHeap_of_n_integers(PairingHeap_class): INPUT: - - ``item`` -- non negative integer; the item to consider + - ``item`` -- nonnegative integer; the item to consider - ``value`` -- the value associated with ``item`` @@ -395,10 +425,14 @@ cdef class PairingHeap_of_n_integers(PairingHeap_class): sage: P.push(11, 2) Traceback (most recent call last): ... - ValueError: item must be in range 0..4 + ValueError: item must be in range [0, 4] + sage: P.push(-1, 0) + Traceback (most recent call last): + ... + OverflowError: can't convert negative value to size_t """ if item >= self.n: - raise ValueError(f"item must be in range 0..{self.n - 1}") + raise ValueError(f"item must be in range [0, {self.n - 1}]") if item in self: raise ValueError(f"{item} is already in the heap") @@ -501,7 +535,7 @@ cdef class PairingHeap_of_n_integers(PairingHeap_class): INPUT: - - ``item`` -- non negative integer; the item to consider + - ``item`` -- nonnegative integer; the item to consider - ``new_value`` -- the new value for ``item`` @@ -534,7 +568,7 @@ cdef class PairingHeap_of_n_integers(PairingHeap_class): ValueError: the new value must be less than the current value """ if item >= self.n: - raise ValueError(f"item must be in range 0..{self.n - 1}") + raise ValueError(f"item must be in range [0, {self.n - 1}]") cdef PairingHeapNodePy * p if bitset_in(self.active, item): p = self.nodes + item @@ -555,7 +589,7 @@ cdef class PairingHeap_of_n_integers(PairingHeap_class): INPUT: - - ``item`` -- non negative integer; the item to consider + - ``item`` -- nonnegative integer; the item to consider EXAMPLES:: @@ -581,7 +615,7 @@ cdef class PairingHeap_of_n_integers(PairingHeap_class): INPUT: - - ``item`` -- non negative integer; the item to consider + - ``item`` -- nonnegative integer; the item to consider EXAMPLES:: @@ -607,7 +641,7 @@ cdef class PairingHeap_of_n_integers(PairingHeap_class): cdef class PairingHeap_of_n_hashables(PairingHeap_class): r""" - Pairing Heap for ``n`` hashable items. + Pairing Heap for `n` hashable items. EXAMPLES:: @@ -615,8 +649,7 @@ cdef class PairingHeap_of_n_hashables(PairingHeap_class): sage: P = PairingHeap_of_n_hashables(5); P PairingHeap_of_n_hashables: capacity 5, size 0 sage: P.push(1, 3) - sage: P.push('abc', 2) - sage: P + sage: P.push('abc', 2); P PairingHeap_of_n_hashables: capacity 5, size 2 sage: P.top() ('abc', 2) @@ -663,6 +696,7 @@ cdef class PairingHeap_of_n_hashables(PairingHeap_class): ... TypeError: unsupported operand parent(s) for >=: 'Integer Ring' and '' """ + def __init__(self, size_t n): r""" Construct the ``PairingHeap_of_n_hashables``. @@ -733,7 +767,7 @@ cdef class PairingHeap_of_n_hashables(PairingHeap_class): INPUT: - - ``item`` -- non negative integer; the item to consider + - ``item`` -- a hashable object; the item to add - ``value`` -- the value associated with ``item`` From 7682e5b9aba11ea0a61ec7fcfc4ced6c97b5204a Mon Sep 17 00:00:00 2001 From: dcoudert Date: Thu, 12 Dec 2024 17:49:09 +0100 Subject: [PATCH 25/27] #39046: suggested changes --- src/sage/data_structures/pairing_heap.pyx | 25 +++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/sage/data_structures/pairing_heap.pyx b/src/sage/data_structures/pairing_heap.pyx index 6a6e61e1c5e..59cd73ddfde 100644 --- a/src/sage/data_structures/pairing_heap.pyx +++ b/src/sage/data_structures/pairing_heap.pyx @@ -207,7 +207,7 @@ cdef class PairingHeap_class: sage: P.size() 1 - One may also use Python's `__len__`:: + One may also use Python's ``__len__``:: sage: len(P) 1 @@ -364,12 +364,18 @@ cdef class PairingHeap_of_n_integers(PairingHeap_class): Traceback (most recent call last): ... ValueError: item must be in range [0, 4] + sage: PairingHeap_of_n_integers(1) + PairingHeap_of_n_integers: capacity 1, size 0 + + TESTS:: + sage: PairingHeap_of_n_integers(0) Traceback (most recent call last): ... ValueError: the capacity of the heap must be strictly positive - sage: PairingHeap_of_n_integers(1) - PairingHeap_of_n_integers: capacity 1, size 0 + sage: P = PairingHeap_of_n_integers(10) + sage: P.push(1, 1); P.push(7, 0); P.push(0, 4); P.pop(); P.push(5, 5) + sage: TestSuite(P).run(skip="_test_pickling") """ if not n: raise ValueError("the capacity of the heap must be strictly positive") @@ -738,6 +744,9 @@ cdef class PairingHeap_of_n_hashables(PairingHeap_class): ValueError: the capacity of the heap must be strictly positive sage: P = PairingHeap_of_n_hashables(1); P PairingHeap_of_n_hashables: capacity 1, size 0 + sage: P = PairingHeap_of_n_hashables(6) + sage: P.push(1, -0.5); P.push(frozenset(), 1); P.pop(); P.push('a', 3.5) + sage: TestSuite(P).run(skip="_test_pickling") """ if not n: raise ValueError("the capacity of the heap must be strictly positive") @@ -1348,7 +1357,7 @@ def _test_PairingHeap_of_n_hashables(n=100): pass -def compare_heaps(n=100, verbose=False): +def _compare_heaps(n=100, verbose=False): r""" Check that the heaps behave the same. @@ -1369,13 +1378,13 @@ def compare_heaps(n=100, verbose=False): EXAMPLES:: - sage: from sage.data_structures.pairing_heap import compare_heaps - sage: compare_heaps(n=100) - sage: compare_heaps(n=100, verbose=True) # random + sage: from sage.data_structures.pairing_heap import _compare_heaps + sage: _compare_heaps(n=100) + sage: _compare_heaps(n=100, verbose=True) # random PairingHeap_of_n_integers: 7.800000000024454e-05 PairingHeap_of_n_hashables: 9.400000000026054e-05 PairingHeap (C++): 6.899999999987472e-05 - sage: compare_heaps(1000000, verbose=True) # not tested (long time), random + sage: _compare_heaps(1000000, verbose=True) # not tested (long time), random PairingHeap_of_n_integers: 1.5106779999999995 PairingHeap_of_n_hashables: 4.998040000000001 PairingHeap (C++): 1.7841750000000012 From abafad9a4769760fce0e5105fbb2f28f01ef7b96 Mon Sep 17 00:00:00 2001 From: dcoudert Date: Fri, 13 Dec 2024 13:24:42 +0100 Subject: [PATCH 26/27] #39046: review comments --- src/sage/data_structures/pairing_heap.h | 14 +++++++------- src/sage/data_structures/pairing_heap.pyx | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/sage/data_structures/pairing_heap.h b/src/sage/data_structures/pairing_heap.h index 81ac55779f6..3c6cbbf4216 100644 --- a/src/sage/data_structures/pairing_heap.h +++ b/src/sage/data_structures/pairing_heap.h @@ -25,7 +25,7 @@ * - top_value(): access the value of the item at the top of the heap in O(1). * This operation assumes that the heap is not empty. * - * - pop(): remove top item from the heap in amortize time O(log(n)) + * - pop(): remove top item from the heap in amortize time O(log(n)). * * - decrease(item, new_value): change the value associated with the item to the * specified value ``new_value`` in time o(log(n)). The new value must be @@ -250,17 +250,17 @@ namespace pairing_heap { // Insert an item into the heap with specified value (priority) void push(const TI &some_item, const TV &some_value) { - if (nodes.find(some_item) != nodes.end()) { + if (contains(some_item)) { throw std::invalid_argument("item already in the heap"); } PairingHeapNode *p = new PairingHeapNode(some_item, some_value); nodes[some_item] = p; - root = root == nullptr ? p : HeapNodeType::_merge(root, p); + root = empty() ? p : HeapNodeType::_merge(root, p); } // Return the top pair (item, value) of the heap std::pair top() const { - if (root == nullptr) { + if (empty()) { throw std::domain_error("trying to access the top of an empty heap"); } return std::make_pair(root->item, root->value); @@ -268,7 +268,7 @@ namespace pairing_heap { // Return the top item of the heap TI top_item() const { - if (root == nullptr) { + if (empty()) { throw std::domain_error("trying to access the top of an empty heap"); } return root->item; @@ -276,7 +276,7 @@ namespace pairing_heap { // Return the top value of the heap TV top_value() const { - if (root == nullptr) { + if (empty()) { throw std::domain_error("trying to access the top of an empty heap"); } return root->value; @@ -284,7 +284,7 @@ namespace pairing_heap { // Remove the top element from the heap. Do nothing if empty void pop() { - if (root != nullptr) { + if (not empty()) { PairingHeapNode *p = root->child; nodes.erase(root->item); delete root; diff --git a/src/sage/data_structures/pairing_heap.pyx b/src/sage/data_structures/pairing_heap.pyx index 59cd73ddfde..8d92e714483 100644 --- a/src/sage/data_structures/pairing_heap.pyx +++ b/src/sage/data_structures/pairing_heap.pyx @@ -1061,7 +1061,7 @@ def _test_PairingHeap_from_C(n=100): # Test decrease key operations. We first push items in the heap with an # excess of k in the value. Then we decrease the keys in a random order by - # random values until returning to the origianl values. We finally check the + # random values until returning to the original values. We finally check the # validity of the resulting ordering. k = 10 dec = {item: k for item in items} From fc81b147a77cdf9cfb8fac9e7c6a93ffcdc5dc05 Mon Sep 17 00:00:00 2001 From: dcoudert Date: Fri, 13 Dec 2024 13:59:54 +0100 Subject: [PATCH 27/27] #39046: typos --- src/sage/data_structures/pairing_heap.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/data_structures/pairing_heap.pyx b/src/sage/data_structures/pairing_heap.pyx index 8d92e714483..ec08be43652 100644 --- a/src/sage/data_structures/pairing_heap.pyx +++ b/src/sage/data_structures/pairing_heap.pyx @@ -1188,7 +1188,7 @@ def _test_PairingHeap_of_n_integers(n=100): # Test decrease key operations. We first push items in the heap with an # excess of k in the value. Then we decrease the keys in a random order by - # random values until returning to the origianl values. We finally check the + # random values until returning to the original values. We finally check the # validity of the resulting ordering. cdef int k = 10 cdef list dec = [k] * n @@ -1294,7 +1294,7 @@ def _test_PairingHeap_of_n_hashables(n=100): # Test decrease key operations. We first push items in the heap with an # excess of k in the value. Then we decrease the keys in a random order by - # random values until returning to the origianl values. We finally check the + # random values until returning to the original values. We finally check the # validity of the resulting ordering. cdef int k = 10 cdef dict dec = {item: k for item in items}