1 /* dzl-heap.c
2 *
3 * Copyright (C) 2014 Christian Hergert <christian@hergert.me>
4 *
5 * This file is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This file is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #define G_LOG_DOMAIN "dzl-heap"
20
21 #include "config.h"
22
23 #include <string.h>
24
25 #include "dzl-heap.h"
26
27 /**
28 * SECTION:dzlheap
29 * @title: Heaps
30 * @short_description: efficient priority queues using min/max heaps
31 *
32 * Heaps are similar to a partially sorted tree but implemented as an
33 * array. They allow for efficient O(1) lookup of the highest priority
34 * item as it will always be the first item of the array.
35 *
36 * To create a new heap use dzl_heap_new().
37 *
38 * To add items to the heap, use dzl_heap_insert_val() or
39 * dzl_heap_insert_vals() to insert in bulk.
40 *
41 * To access an item in the heap, use dzl_heap_index().
42 *
43 * To remove an arbitrary item from the heap, use dzl_heap_extract_index().
44 *
45 * To remove the highest priority item in the heap, use dzl_heap_extract().
46 *
47 * To free a heap, use dzl_heap_unref().
48 *
49 * Here is an example that stores integers in a #DzlHeap:
50 * |[<!-- language="C" -->
51 * static int
52 * cmpint (gconstpointer a,
53 * gconstpointer b)
54 * {
55 * return *(const gint *)a - *(const gint *)b;
56 * }
57 *
58 * int
59 * main (gint argc,
60 * gchar *argv[])
61 * {
62 * DzlHeap *heap;
63 * gint i;
64 * gint v;
65 *
66 * heap = dzl_heap_new (sizeof (gint), cmpint);
67 *
68 * for (i = 0; i < 10000; i++)
69 * dzl_heap_insert_val (heap, i);
70 * for (i = 0; i < 10000; i++)
71 * dzl_heap_extract (heap, &v);
72 *
73 * dzl_heap_unref (heap);
74 * }
75 * ]|
76 */
77
78 #define MIN_HEAP_SIZE 16
79
80 /*
81 * Based upon Mastering Algorithms in C by Kyle Loudon.
82 * Section 10 - Heaps and Priority Queues.
83 */
84
85 G_DEFINE_BOXED_TYPE (DzlHeap, dzl_heap, dzl_heap_ref, dzl_heap_unref)
86
87 typedef struct _DzlHeapReal DzlHeapReal;
88
89 struct _DzlHeapReal
90 {
91 gchar *data;
92 gssize len;
93 volatile gint ref_count;
94 guint element_size;
95 gsize allocated_len;
96 GCompareFunc compare;
97 gchar tmp[0];
98 };
99
100 #define heap_parent(npos) (((npos)-1)/2)
101 #define heap_left(npos) (((npos)*2)+1)
102 #define heap_right(npos) (((npos)*2)+2)
103 #define heap_index(h,i) ((h)->data + (i * (h)->element_size))
104 #define heap_compare(h,a,b) ((h)->compare(heap_index(h,a), heap_index(h,b)))
105 #define heap_swap(h,a,b) \
106 G_STMT_START { \
107 memcpy ((h)->tmp, heap_index (h, a), (h)->element_size); \
108 memcpy (heap_index (h, a), heap_index (h, b), (h)->element_size); \
109 memcpy (heap_index (h, b), (h)->tmp, (h)->element_size); \
110 } G_STMT_END
111
112 /**
113 * dzl_heap_new:
114 * @element_size: the size of each element in the heap
115 * @compare_func: (scope async): a function to compare to elements
116 *
117 * Creates a new #DzlHeap. A heap is a tree-like structure stored in
118 * an array that is not fully sorted, but head is guaranteed to be either
119 * the max, or min value based on @compare_func. This is also known as
120 * a priority queue.
121 *
122 * Returns: (transfer full): A newly allocated #DzlHeap
123 */
124 DzlHeap *
dzl_heap_new(guint element_size,GCompareFunc compare_func)125 dzl_heap_new (guint element_size,
126 GCompareFunc compare_func)
127 {
128 DzlHeapReal *real;
129
130 g_return_val_if_fail (element_size, NULL);
131 g_return_val_if_fail (compare_func, NULL);
132
133 real = g_malloc_n (1, sizeof (DzlHeapReal) + element_size);
134 real->data = NULL;
135 real->len = 0;
136 real->ref_count = 1;
137 real->element_size = element_size;
138 real->allocated_len = 0;
139 real->compare = compare_func;
140
141 return (DzlHeap *)real;
142 }
143
144 /**
145 * dzl_heap_ref:
146 * @heap: An #DzlHeap
147 *
148 * Increments the reference count of @heap by one.
149 *
150 * Returns: (transfer full): @heap
151 */
152 DzlHeap *
dzl_heap_ref(DzlHeap * heap)153 dzl_heap_ref (DzlHeap *heap)
154 {
155 DzlHeapReal *real = (DzlHeapReal *)heap;
156
157 g_return_val_if_fail (heap, NULL);
158 g_return_val_if_fail (real->ref_count, NULL);
159
160 g_atomic_int_inc (&real->ref_count);
161
162 return heap;
163 }
164
165 static void
dzl_heap_real_free(DzlHeapReal * real)166 dzl_heap_real_free (DzlHeapReal *real)
167 {
168 g_assert (real);
169 g_assert_cmpint (real->ref_count, ==, 0);
170
171 g_free (real->data);
172 g_free (real);
173 }
174
175 /**
176 * dzl_heap_unref:
177 * @heap: (transfer full): An #DzlHeap
178 *
179 * Decrements the reference count of @heap by one, freeing the structure
180 * when the reference count reaches zero.
181 */
182 void
dzl_heap_unref(DzlHeap * heap)183 dzl_heap_unref (DzlHeap *heap)
184 {
185 DzlHeapReal *real = (DzlHeapReal *)heap;
186
187 g_return_if_fail (heap);
188 g_return_if_fail (real->ref_count);
189
190 if (g_atomic_int_dec_and_test (&real->ref_count))
191 dzl_heap_real_free (real);
192 }
193
194 static void
dzl_heap_real_grow(DzlHeapReal * real)195 dzl_heap_real_grow (DzlHeapReal *real)
196 {
197 g_assert (real);
198 g_assert_cmpint (real->allocated_len, <, G_MAXSIZE / 2);
199
200 real->allocated_len = MAX (MIN_HEAP_SIZE, (real->allocated_len * 2));
201 real->data = g_realloc_n (real->data,
202 real->allocated_len,
203 real->element_size);
204 }
205
206 static void
dzl_heap_real_shrink(DzlHeapReal * real)207 dzl_heap_real_shrink (DzlHeapReal *real)
208 {
209 g_assert (real);
210 g_assert ((real->allocated_len / 2) >= (gsize)real->len);
211
212 real->allocated_len = MAX (MIN_HEAP_SIZE, real->allocated_len / 2);
213 real->data = g_realloc_n (real->data,
214 real->allocated_len,
215 real->element_size);
216 }
217
218 static void
dzl_heap_real_insert_val(DzlHeapReal * real,gconstpointer data)219 dzl_heap_real_insert_val (DzlHeapReal *real,
220 gconstpointer data)
221 {
222 gint ipos;
223 gint ppos;
224
225 g_assert (real);
226 g_assert (data);
227
228 if (G_UNLIKELY ((gsize)real->len == real->allocated_len))
229 dzl_heap_real_grow (real);
230
231 memcpy (real->data + (real->element_size * real->len),
232 data,
233 real->element_size);
234
235 ipos = real->len;
236 ppos = heap_parent (ipos);
237
238 while ((ipos > 0) && (heap_compare (real, ppos, ipos) < 0))
239 {
240 heap_swap (real, ppos, ipos);
241 ipos = ppos;
242 ppos = heap_parent (ipos);
243 }
244
245 real->len++;
246 }
247
248 void
dzl_heap_insert_vals(DzlHeap * heap,gconstpointer data,guint len)249 dzl_heap_insert_vals (DzlHeap *heap,
250 gconstpointer data,
251 guint len)
252 {
253 DzlHeapReal *real = (DzlHeapReal *)heap;
254 const gchar *ptr = data;
255 guint i;
256
257 g_return_if_fail (heap);
258 g_return_if_fail (data);
259 g_return_if_fail (len);
260 g_return_if_fail ((G_MAXSSIZE - len) > real->len);
261
262 for (i = 0; i < len; i++, ptr += real->element_size)
263 dzl_heap_real_insert_val (real, ptr);
264 }
265
266 gboolean
dzl_heap_extract(DzlHeap * heap,gpointer result)267 dzl_heap_extract (DzlHeap *heap,
268 gpointer result)
269 {
270 DzlHeapReal *real = (DzlHeapReal *)heap;
271 gint ipos;
272 gint lpos;
273 gint rpos;
274 gint mpos;
275
276 g_return_val_if_fail (heap, FALSE);
277
278 if (real->len == 0)
279 return FALSE;
280
281 if (result)
282 memcpy (result, heap_index (real, 0), real->element_size);
283
284 if (--real->len > 0)
285 {
286 memmove (real->data,
287 heap_index (real, real->len),
288 real->element_size);
289
290 ipos = 0;
291
292 while (TRUE)
293 {
294 lpos = heap_left (ipos);
295 rpos = heap_right (ipos);
296
297 if ((lpos < real->len) && (heap_compare (real, lpos, ipos) > 0))
298 mpos = lpos;
299 else
300 mpos = ipos;
301
302 if ((rpos < real->len) && (heap_compare (real, rpos, mpos) > 0))
303 mpos = rpos;
304
305 if (mpos == ipos)
306 break;
307
308 heap_swap (real, mpos, ipos);
309
310 ipos = mpos;
311 }
312 }
313
314 if ((real->len > MIN_HEAP_SIZE) && (real->allocated_len / 2) >= (gsize)real->len)
315 dzl_heap_real_shrink (real);
316
317 return TRUE;
318 }
319
320 gboolean
dzl_heap_extract_index(DzlHeap * heap,gsize index_,gpointer result)321 dzl_heap_extract_index (DzlHeap *heap,
322 gsize index_,
323 gpointer result)
324 {
325 DzlHeapReal *real = (DzlHeapReal *)heap;
326 gssize ipos;
327 gssize lpos;
328 gssize mpos;
329 gssize ppos;
330 gssize rpos;
331
332 g_return_val_if_fail (heap, FALSE);
333 g_return_val_if_fail (index_ < G_MAXSSIZE, FALSE);
334 g_return_val_if_fail (index_ < (gsize)real->len, FALSE);
335
336 if (real->len <= 0)
337 return FALSE;
338
339 if (result)
340 memcpy (result, heap_index (real, index_), real->element_size);
341
342 real->len--;
343
344 if (real->len > 0 && index_ != (gsize)real->len)
345 {
346 memcpy (heap_index (real, index_),
347 heap_index (real, real->len),
348 real->element_size);
349
350 ipos = index_;
351 ppos = heap_parent (ipos);
352
353 while (heap_compare (real, ipos, ppos) > 0)
354 {
355 heap_swap (real, ipos, ppos);
356 ipos = ppos;
357 ppos = heap_parent (ppos);
358 }
359
360 if (ipos == (gssize)index_)
361 {
362 while (TRUE)
363 {
364 lpos = heap_left (ipos);
365 rpos = heap_right (ipos);
366
367 if ((lpos < real->len) && (heap_compare (real, lpos, ipos) > 0))
368 mpos = lpos;
369 else
370 mpos = ipos;
371
372 if ((rpos < real->len) && (heap_compare (real, rpos, mpos) > 0))
373 mpos = rpos;
374
375 if (mpos == ipos)
376 break;
377
378 heap_swap (real, mpos, ipos);
379
380 ipos = mpos;
381 }
382 }
383 }
384
385 if ((real->len > MIN_HEAP_SIZE) && (real->allocated_len / 2) >= (gsize)real->len)
386 dzl_heap_real_shrink (real);
387
388 return TRUE;
389 }
390