/*- * Copyright 2016 Vsevolod Stakhov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "config.h" #include "libutil/heap.h" struct rspamd_min_heap { GPtrArray *ar; }; #define __SWAP(a, b) do { \ __typeof__(a) _a = (a); \ __typeof__(b) _b = (b); \ a = _b; \ b = _a; \ } while (0) #define heap_swap(h,e1,e2) do { \ __SWAP((h)->ar->pdata[(e1)->idx - 1], (h)->ar->pdata[(e2)->idx - 1]); \ __SWAP((e1)->idx, (e2)->idx); \ } while (0) #define min_elt(e1, e2) ((e1)->pri <= (e2)->pri ? (e1) : (e2)) /* * Swims element added (or changed) to preserve heap's invariant */ static void rspamd_min_heap_swim (struct rspamd_min_heap *heap, struct rspamd_min_heap_elt *elt) { struct rspamd_min_heap_elt *parent; while (elt->idx > 1) { parent = g_ptr_array_index (heap->ar, elt->idx / 2 - 1); if (parent->pri > elt->pri) { heap_swap (heap, elt, parent); } else { break; } } } /* * Sinks the element popped (or changed) to preserve heap's invariant */ static void rspamd_min_heap_sink (struct rspamd_min_heap *heap, struct rspamd_min_heap_elt *elt) { struct rspamd_min_heap_elt *c1, *c2, *m; while (elt->idx * 2 < heap->ar->len) { c1 = g_ptr_array_index (heap->ar, elt->idx * 2 - 1); c2 = g_ptr_array_index (heap->ar, elt->idx * 2); m = min_elt (c1, c2); if (elt->pri > m->pri) { heap_swap (heap, elt, m); } else { break; } } if (elt->idx * 2 - 1 < heap->ar->len) { m = g_ptr_array_index (heap->ar, elt->idx * 2 - 1); if (elt->pri > m->pri) { heap_swap (heap, elt, m); } } } struct rspamd_min_heap * rspamd_min_heap_create (gsize reserved_size) { struct rspamd_min_heap *heap; heap = g_malloc (sizeof (*heap)); heap->ar = g_ptr_array_sized_new (reserved_size); return heap; } void rspamd_min_heap_push (struct rspamd_min_heap *heap, struct rspamd_min_heap_elt *elt) { g_assert (heap != NULL); g_assert (elt != NULL); /* Add to the end */ elt->idx = heap->ar->len + 1; g_ptr_array_add (heap->ar, elt); /* Now swim it up */ rspamd_min_heap_swim (heap, elt); } struct rspamd_min_heap_elt* rspamd_min_heap_pop (struct rspamd_min_heap *heap) { struct rspamd_min_heap_elt *elt, *last; g_assert (heap != NULL); if (heap->ar->len == 0) { return NULL; } elt = g_ptr_array_index (heap->ar, 0); last = g_ptr_array_index (heap->ar, heap->ar->len - 1); if (elt != last) { /* Now replace elt with the last element and sink it if needed */ heap_swap (heap, elt, last); g_ptr_array_remove_index_fast (heap->ar, heap->ar->len - 1); rspamd_min_heap_sink (heap, last); } else { g_ptr_array_remove_index_fast (heap->ar, heap->ar->len - 1); } return elt; } void rspamd_min_heap_update_elt (struct rspamd_min_heap *heap, struct rspamd_min_heap_elt *elt, guint npri) { guint oldpri; g_assert (heap != NULL); g_assert (elt->idx > 0 && elt->idx <= heap->ar->len); oldpri = elt->pri; elt->pri = npri; if (npri > oldpri) { /* We might need to sink */ rspamd_min_heap_sink (heap, elt); } else if (npri < oldpri) { /* We might need to swim */ rspamd_min_heap_swim (heap, elt); } } void rspamd_min_heap_remove_elt (struct rspamd_min_heap *heap, struct rspamd_min_heap_elt *elt) { struct rspamd_min_heap_elt *first; g_assert (heap != NULL); g_assert (elt->idx > 0 && elt->idx <= heap->ar->len); first = g_ptr_array_index (heap->ar, 0); if (elt != first) { elt->pri = first->pri - 1; rspamd_min_heap_swim (heap, elt); } /* Now the desired element is on the top of queue */ (void)rspamd_min_heap_pop (heap); } void rspamd_min_heap_destroy (struct rspamd_min_heap *heap) { if (heap) { g_ptr_array_free (heap->ar, TRUE); g_free (heap); } } struct rspamd_min_heap_elt* rspamd_min_heap_index (struct rspamd_min_heap *heap, guint idx) { g_assert (heap != NULL); g_assert (idx < heap->ar->len); return g_ptr_array_index (heap->ar, idx); }