1 /*
2 * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 */
6
7 #ifndef _PICO_UTIL_PHEAP_H
8 #define _PICO_UTIL_PHEAP_H
9
10 #include "pico.h"
11
12 #ifdef __cplusplus
13 extern "C" {
14 #endif
15
16 // PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_PHEAP, Enable/disable assertions in the pheap module, type=bool, default=0, group=pico_util
17 #ifndef PARAM_ASSERTIONS_ENABLED_PHEAP
18 #define PARAM_ASSERTIONS_ENABLED_PHEAP 0
19 #endif
20
21 /**
22 * \file pheap.h
23 * \defgroup util_pheap pheap
24 * Pairing Heap Implementation
25 * \ingroup pico_util
26 *
27 * pheap defines a simple pairing heap. The implementation simply tracks array indexes, it is up to
28 * the user to provide storage for heap entries and a comparison function.
29 *
30 * NOTE: This class is not safe for concurrent usage. It should be externally protected. Furthermore
31 * if used concurrently, the caller needs to protect around their use of the returned id.
32 * For example, ph_remove_and_free_head returns the id of an element that is no longer in the heap.
33 * The user can still use this to look at the data in their companion array, however obviously further operations
34 * on the heap may cause them to overwrite that data as the id may be reused on subsequent operations
35 *
36 */
37 // PICO_CONFIG: PICO_PHEAP_MAX_ENTRIES, Maximum number of entries in the pheap, min=1, max=65534, default=255, group=pico_util
38 #ifndef PICO_PHEAP_MAX_ENTRIES
39 #define PICO_PHEAP_MAX_ENTRIES 255
40 #endif
41
42 // public heap_node ids are numbered from 1 (0 means none)
43 #if PICO_PHEAP_MAX_ENTRIES < 256
44 typedef uint8_t pheap_node_id_t;
45 #elif PICO_PHEAP_MAX_ENTRIES < 65535
46 typedef uint16_t pheap_node_id_t;
47 #else
48 #error invalid PICO_PHEAP_MAX_ENTRIES
49 #endif
50
51 typedef struct pheap_node {
52 pheap_node_id_t child, sibling, parent;
53 } pheap_node_t;
54
55 /**
56 * A user comparator function for nodes in a pairing heap.
57 *
58 * \return true if a < b in natural order. Note this relative ordering must be stable from call to call.
59 */
60 typedef bool (*pheap_comparator)(void *user_data, pheap_node_id_t a, pheap_node_id_t b);
61
62 typedef struct pheap {
63 pheap_node_t *nodes;
64 pheap_comparator comparator;
65 void *user_data;
66 pheap_node_id_t max_nodes;
67 pheap_node_id_t root_id;
68 // we remove from head and add to tail to stop reusing the same ids
69 pheap_node_id_t free_head_id;
70 pheap_node_id_t free_tail_id;
71 } pheap_t;
72
73 /**
74 * Create a pairing heap, which effectively maintains an efficient sorted ordering
75 * of nodes. The heap itself stores no user per-node state, it is expected
76 * that the user maintains a companion array. A comparator function must
77 * be provided so that the heap implementation can determine the relative ordering of nodes
78 *
79 * \param max_nodes the maximum number of nodes that may be in the heap (this is bounded by
80 * PICO_PHEAP_MAX_ENTRIES which defaults to 255 to be able to store indexes
81 * in a single byte).
82 * \param comparator the node comparison function
83 * \param user_data a user data pointer associated with the heap that is provided in callbacks
84 * \return a newly allocated and initialized heap
85 */
86 pheap_t *ph_create(uint max_nodes, pheap_comparator comparator, void *user_data);
87
88 /**
89 * Removes all nodes from the pairing heap
90 * \param heap the heap
91 */
92 void ph_clear(pheap_t *heap);
93
94 /**
95 * De-allocates a pairing heap
96 *
97 * Note this method must *ONLY* be called on heaps created by ph_create()
98 * \param heap the heap
99 */
100 void ph_destroy(pheap_t *heap);
101
102 // internal method
ph_get_node(pheap_t * heap,pheap_node_id_t id)103 static inline pheap_node_t *ph_get_node(pheap_t *heap, pheap_node_id_t id) {
104 assert(id && id <= heap->max_nodes);
105 return heap->nodes + id - 1;
106 }
107
108 // internal method
ph_add_child_node(pheap_t * heap,pheap_node_id_t parent_id,pheap_node_id_t child_id)109 static void ph_add_child_node(pheap_t *heap, pheap_node_id_t parent_id, pheap_node_id_t child_id) {
110 pheap_node_t *n = ph_get_node(heap, parent_id);
111 assert(parent_id);
112 assert(child_id);
113 assert(parent_id != child_id);
114 pheap_node_t *c = ph_get_node(heap, child_id);
115 c->parent = parent_id;
116 if (!n->child) {
117 n->child = child_id;
118 } else {
119 c->sibling = n->child;
120 n->child = child_id;
121 }
122 }
123
124 // internal method
ph_merge_nodes(pheap_t * heap,pheap_node_id_t a,pheap_node_id_t b)125 static pheap_node_id_t ph_merge_nodes(pheap_t *heap, pheap_node_id_t a, pheap_node_id_t b) {
126 if (!a) return b;
127 if (!b) return a;
128 if (heap->comparator(heap->user_data, a, b)) {
129 ph_add_child_node(heap, a, b);
130 return a;
131 } else {
132 ph_add_child_node(heap, b, a);
133 return b;
134 }
135 }
136
137 /**
138 * Allocate a new node from the unused space in the heap
139 *
140 * \param heap the heap
141 * \return an identifier for the node, or 0 if the heap is full
142 */
ph_new_node(pheap_t * heap)143 static inline pheap_node_id_t ph_new_node(pheap_t *heap) {
144 if (!heap->free_head_id) return 0;
145 pheap_node_id_t id = heap->free_head_id;
146 pheap_node_t *hn = ph_get_node(heap, id);
147 heap->free_head_id = hn->sibling;
148 if (!heap->free_head_id) heap->free_tail_id = 0;
149 hn->child = hn->sibling = hn->parent = 0;
150 return id;
151 }
152
153 /**
154 * Inserts a node into the heap.
155 *
156 * This method inserts a node (previously allocated by ph_new_node())
157 * into the heap, determining the correct order by calling
158 * the heap's comparator
159 *
160 * \param heap the heap
161 * \param id the id of the node to insert
162 * \return the id of the new head of the pairing heap (i.e. node that compares first)
163 */
ph_insert_node(pheap_t * heap,pheap_node_id_t id)164 static inline pheap_node_id_t ph_insert_node(pheap_t *heap, pheap_node_id_t id) {
165 assert(id);
166 pheap_node_t *hn = ph_get_node(heap, id);
167 hn->child = hn->sibling = hn->parent = 0;
168 heap->root_id = ph_merge_nodes(heap, heap->root_id, id);
169 return heap->root_id;
170 }
171
172 /**
173 * Returns the head node in the heap, i.e. the node
174 * which compares first, but without removing it from the heap.
175 *
176 * \param heap the heap
177 * \return the current head node id
178 */
ph_peek_head(pheap_t * heap)179 static inline pheap_node_id_t ph_peek_head(pheap_t *heap) {
180 return heap->root_id;
181 }
182
183 /**
184 * Remove the head node from the pairing heap. This head node is
185 * the node which compares first in the logical ordering provided
186 * by the comparator.
187 *
188 * Note that in the case of free == true, the returned id is no longer
189 * allocated and may be re-used by future node allocations, so the caller
190 * should retrieve any per node state from the companion array before modifying
191 * the heap further.
192 *
193 * @param heap the heap
194 * @param free true if the id is also to be freed; false if not - useful if the caller
195 * may wish to re-insert an item with the same id)
196 * @return the old head node id.
197 */
198 pheap_node_id_t ph_remove_head(pheap_t *heap, bool free);
199
200 /**
201 * Remove the head node from the pairing heap. This head node is
202 * the node which compares first in the logical ordering provided
203 * by the comparator.
204 *
205 * Note that the returned id will be freed, and thus may be re-used by future node allocations,
206 * so the caller should retrieve any per node state from the companion array before modifying
207 * the heap further.
208 *
209 * @param heap the heap
210 * @return the old head node id.
211 */
ph_remove_and_free_head(pheap_t * heap)212 static inline pheap_node_id_t ph_remove_and_free_head(pheap_t *heap) {
213 return ph_remove_head(heap, true);
214 }
215
216 /**
217 * Remove and free an arbitrary node from the pairing heap. This is a more
218 * costly operation than removing the head via ph_remove_and_free_head()
219 *
220 * @param heap the heap
221 * @param id the id of the node to free
222 * @return true if the the node was in the heap, false otherwise
223 */
224 bool ph_remove_and_free_node(pheap_t *heap, pheap_node_id_t id);
225
226 /**
227 * Determine if the heap contains a given node. Note containment refers
228 * to whether the node is inserted (ph_insert_node()) vs allocated (ph_new_node())
229 *
230 * @param heap the heap
231 * @param id the id of the node
232 * @return true if the heap contains a node with the given id, false otherwise.
233 */
ph_contains_node(pheap_t * heap,pheap_node_id_t id)234 static inline bool ph_contains_node(pheap_t *heap, pheap_node_id_t id) {
235 return id == heap->root_id || ph_get_node(heap, id)->parent;
236 }
237
238
239 /**
240 * Free a node that is not currently in the heap, but has been allocated
241 *
242 * @param heap the heap
243 * @param id the id of the node
244 */
ph_free_node(pheap_t * heap,pheap_node_id_t id)245 static inline void ph_free_node(pheap_t *heap, pheap_node_id_t id) {
246 assert(id && !ph_contains_node(heap, id));
247 if (heap->free_tail_id) {
248 ph_get_node(heap, heap->free_tail_id)->sibling = id;
249 }
250 if (!heap->free_head_id) {
251 assert(!heap->free_tail_id);
252 heap->free_head_id = id;
253 }
254 heap->free_tail_id = id;
255 }
256
257 /**
258 * Print a representation of the heap for debugging
259 *
260 * @param heap the heap
261 * @param dump_key a method to print a node value
262 * @param user_data the user data to pass to the dump_key method
263 */
264 void ph_dump(pheap_t *heap, void (*dump_key)(pheap_node_id_t id, void *user_data), void *user_data);
265
266 /**
267 * Initialize a statically allocated heap (ph_create() using the C heap).
268 * The heap member `nodes` must be allocated of size max_nodes.
269 *
270 * @param heap the heap
271 * @param max_nodes the max number of nodes in the heap (matching the size of the heap's nodes array)
272 * @param comparator the comparator for the heap
273 * @param user_data the user data for the heap.
274 */
275 void ph_post_alloc_init(pheap_t *heap, uint max_nodes, pheap_comparator comparator, void *user_data);
276
277 /**
278 * Define a statically allocated pairing heap. This must be initialized
279 * by ph_post_alloc_init
280 */
281 #define PHEAP_DEFINE_STATIC(name, _max_nodes) \
282 static_assert(_max_nodes && _max_nodes < (1u << (8 * sizeof(pheap_node_id_t))), ""); \
283 static pheap_node_t name ## _nodes[_max_nodes]; \
284 static pheap_t name = { \
285 .nodes = name ## _nodes, \
286 .max_nodes = _max_nodes \
287 };
288
289
290 #ifdef __cplusplus
291 }
292 #endif
293
294 #endif
295