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