#include "d_heap.h" ID id_cmp; // <=> ID id_ivar_values; ID id_ivar_scores; ID id_ivar_d; #define Get_DHeap(hobj, hstruct) ((hstruct) = get_dheap_struct(hobj)) #define DHEAP_IDX_LAST(heap) (DHEAP_SIZE(heap) - 1) #define DHEAP_IDX_PARENT(heap, idx) ((idx - 1) / heap->d) #define DHEAP_IDX_CHILD0(heap, idx) ((idx * heap->d) + 1) #define DHEAP_SIZE(heap) (RARRAY_LEN((heap)->scores)) #define DHEAP_VALUE(heap, idx) RARRAY_AREF((heap)->values, idx) #define DHEAP_SCORE(heap, idx) RARRAY_AREF((heap)->scores, idx) #define DHEAP_ASSIGN(heap, idx, scr, val) \ rb_ary_store(heap->scores, idx, scr); \ rb_ary_store(heap->values, idx, val); #define DHEAP_APPEND(heap, scr, val) \ rb_ary_push((heap)->scores, scr); \ rb_ary_push((heap)->values, val); #define DHEAP_DROP_LAST(heap) ( \ rb_ary_pop(heap->scores), \ rb_ary_pop(heap->values) \ ) // score, value #define DHEAP_Check_d_size(d) \ if (d < 2) { \ rb_raise(rb_eArgError, "DHeap d=%d is too small", d); \ } \ if (d > DHEAP_MAX_D) { \ rb_raise(rb_eArgError, "DHeap d=%d is too large", d); \ } #define DHEAP_Check_Index(index, last_index) \ if (index < 0) { \ rb_raise(rb_eIndexError, "DHeap index %ld too small", index); \ } \ else if (last_index < index) { \ rb_raise(rb_eIndexError, "DHeap index %ld too large", index); \ } struct dheap_struct { int d; VALUE scores; VALUE values; }; typedef struct dheap_struct dheap_t; static void dheap_compact(void *ptr) { dheap_t *heap = ptr; if (heap->scores) dheap_gc_location( heap->scores ); if (heap->values) dheap_gc_location( heap->values ); } static void dheap_mark(void *ptr) { dheap_t *heap = ptr; if (heap->scores) rb_gc_mark_movable(heap->scores); if (heap->values) rb_gc_mark_movable(heap->values); } static size_t dheap_memsize(const void *ptr) { const dheap_t *heap = ptr; size_t size = sizeof(*heap); return size; } static const rb_data_type_t dheap_data_type = { "DHeap", { (void (*)(void*))dheap_mark, (void (*)(void*))RUBY_DEFAULT_FREE, (size_t (*)(const void *))dheap_memsize, dheap_compact_callback(dheap_compact), }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, }; static VALUE dheap_s_alloc(VALUE klass) { VALUE obj; dheap_t *heap; obj = TypedData_Make_Struct(klass, dheap_t, &dheap_data_type, heap); heap->d = DHEAP_DEFAULT_D; heap->scores = Qnil; heap->values = Qnil; return obj; } static inline dheap_t * get_dheap_struct(VALUE self) { dheap_t *heap; TypedData_Get_Struct(self, dheap_t, &dheap_data_type, heap); Check_Type(heap->scores, T_ARRAY); return heap; } /* * @overload initialize(d = DHeap::DEFAULT_D) * Initialize a _d_-ary min-heap. * * @param d [Integer] maximum number of children per parent */ static VALUE dheap_initialize(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 0, 1); dheap_t *heap; TypedData_Get_Struct(self, dheap_t, &dheap_data_type, heap); int d = DHEAP_DEFAULT_D; if (argc) { d = NUM2INT(argv[0]); } DHEAP_Check_d_size(d); heap->d = d; heap->scores = rb_ary_new_capa(10000); heap->values = rb_ary_new_capa(10000); return self; } /* static inline VALUE make_dheap_element(VALUE score, VALUE value) { elem_t *elem; VALUE obj = TypedData_Make_Struct( rb_cObject, elem_t, &dheap_elem_type, elem); elem->score = score; elem->value = value; return obj; } #define IsDHeapElem(value) rb_typeddata_is_kind_of((value), &dheap_elem_type) #define Get_DHeap_Elem(value) \ TypedData_Get_Struct((obj), elem_t, &dheap_elem_type, (elem)) static inline elem_t * get_dheap_element(VALUE obj) { elem_t *elem; TypedData_Get_Struct((obj), elem_t, &dheap_elem_type, (elem)); return elem; } */ #define CMP_LT(a, b) (optimized_cmp(a, b) < 0) #define CMP_LTE(a, b) (optimized_cmp(a, b) <= 0) #define CMP_GT(a, b) (optimized_cmp(a, b) > 0) #define CMP_GTE(a, b) (optimized_cmp(a, b) >= 0) /* * short-circuit evaluation for a few basic types. * * Only Integer, Float, and String are optimized, * and only when both arguments are the same type. */ static inline int optimized_cmp(VALUE a, VALUE b) { if (a == b) // Fixnum equality and object equality return 0; if (FIXNUM_P(a) && FIXNUM_P(b)) return (FIX2LONG(a) < FIX2LONG(b)) ? -1 : 1; if (RB_FLOAT_TYPE_P(a) && RB_FLOAT_TYPE_P(b)) { double x, y; x = RFLOAT_VALUE(a); y = RFLOAT_VALUE(b); if (isnan(x) || isnan(y)) rb_cmperr(a, b); // raise ArgumentError return (x < y) ? -1 : ((x == y) ? 0 : 1); } if (RB_TYPE_P(a, T_BIGNUM) && RB_TYPE_P(b, T_BIGNUM)) return FIX2INT(rb_big_cmp(a, b)); if (STRING_P(a) && STRING_P(b)) return rb_str_cmp(a, b); // give up on an optimized version and just call (a <=> b) return rb_cmpint(rb_funcallv(a, id_cmp, 1, &b), a, b); } VALUE dheap_ary_sift_up(dheap_t *heap, long sift_index) { long last_index = DHEAP_IDX_LAST(heap); DHEAP_Check_Index(sift_index, last_index); VALUE sift_value = DHEAP_VALUE(heap, sift_index); VALUE sift_score = DHEAP_SCORE(heap, sift_index); // sift it up to where it belongs for (long parent_index; 0 < sift_index; sift_index = parent_index) { debug(rb_sprintf("sift up(%"PRIsVALUE", %d, %ld)", heap->values, heap->d, sift_index)); parent_index = DHEAP_IDX_PARENT(heap, sift_index); VALUE parent_score = DHEAP_SCORE(heap, parent_index); // parent is smaller: heap is restored if (CMP_LTE(parent_score, sift_score)) break; // parent is larger: swap and continue sifting up VALUE parent_value = DHEAP_VALUE(heap, parent_index); DHEAP_ASSIGN(heap, sift_index, parent_score, parent_value); DHEAP_ASSIGN(heap, parent_index, sift_score, sift_value); } debug(rb_sprintf("sifted (%"PRIsVALUE", %d, %ld)", heap->values, heap->d, sift_index)); return LONG2NUM(sift_index); } VALUE dheap_ary_sift_down(dheap_t *heap, long sift_index) { long last_index = DHEAP_IDX_LAST(heap); DHEAP_Check_Index(sift_index, last_index); VALUE sift_value = DHEAP_VALUE(heap, sift_index); VALUE sift_score = DHEAP_SCORE(heap, sift_index); // iteratively sift it down to where it belongs for (long child_index; sift_index < last_index; sift_index = child_index) { debug(rb_sprintf("sift dn(%"PRIsVALUE", %d, %ld)", heap->values, heap->d, sift_index)); // find first child index, and break if we've reached the last layer long child_idx0 = child_index = DHEAP_IDX_CHILD0(heap, sift_index); if (last_index < child_idx0) break; // find the min child (and its child_index) // requires "d" comparisons to find min child and compare to sift_score long last_sibidx = child_idx0 + heap->d - 1; if (last_index < last_sibidx) last_sibidx = last_index; VALUE child_score = DHEAP_SCORE(heap, child_idx0); child_index = child_idx0; for (long sibling_index = child_idx0 + 1; sibling_index <= last_sibidx; ++sibling_index) { VALUE sibling_score = DHEAP_SCORE(heap, sibling_index); if (CMP_LT(sibling_score, child_score)) { child_score = sibling_score; child_index = sibling_index; } } // child is larger: heap is restored if (CMP_LTE(sift_score, child_score)) break; // child is smaller: swap and continue sifting down VALUE child_value = DHEAP_VALUE(heap, child_index); DHEAP_ASSIGN(heap, sift_index, child_score, child_value); DHEAP_ASSIGN(heap, child_index, sift_score, sift_value); } debug(rb_sprintf("sifted (%"PRIsVALUE", %d, %ld)", heap->values, heap->d, sift_index)); return LONG2NUM(sift_index); } /* * @return [Integer] the number of elements in the heap */ static VALUE dheap_size(VALUE self) { dheap_t *heap = get_dheap_struct(self); long size = DHEAP_SIZE(heap); return LONG2NUM(size); } /* * @return [Boolean] is the heap empty? */ static VALUE dheap_empty_p(VALUE self) { dheap_t *heap = get_dheap_struct(self); long size = DHEAP_SIZE(heap); return size == 0 ? Qtrue : Qfalse; } /* * @return [Integer] the maximum number of children per parent */ static VALUE dheap_attr_d(VALUE self) { dheap_t *heap = get_dheap_struct(self); return INT2FIX(heap->d); } /* * Freezes the heap as well as its underlying array, but does not * deep-freeze the elements in the heap. * * @return [self] */ static VALUE dheap_freeze(VALUE self) { dheap_t *heap = get_dheap_struct(self); ID id_freeze = rb_intern("freeze"); rb_funcall(heap->scores, id_freeze, 0); rb_funcall(heap->values, id_freeze, 0); return rb_call_super(0, NULL); } /* * @overload push(score, value = score) * * Push a value onto heap, using a score to determine sort-order. * * Ideally, the score should be a frozen value that can be efficiently compared * to other scores, e.g. an Integer or Float or (maybe) a String * * Time complexity: O(log n / log d) (worst-case) * * @param score [#<=>] a value that can be compared to other scores. * @param value [Object] an object that is associated with the score. * * @return [Integer] the index of the value's final position. */ static VALUE dheap_push(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 1, 2); VALUE scr = argv[0]; VALUE val = argc < 2 ? scr : argv[1]; dheap_t *heap = get_dheap_struct(self); DHEAP_APPEND(heap, scr, val); long last_index = DHEAP_IDX_LAST(heap); return dheap_ary_sift_up(heap, last_index); } /* * Pushes a comparable value onto the heap. * * The value will be its own score. * * Time complexity: O(log n / log d) (worst-case) * * @param value [#<=>] a value that can be compared to other heap members. * @return [self] */ static VALUE dheap_left_shift(VALUE self, VALUE value) { dheap_push(1, &value, self); return self; } /* dheap_t hstruct; \ Get_DHeap(self, hstruct); \ VALUE values = hstruct.values; \ VALUE scores = hstruct.scores; \ VALUE dval = INT2FIX(hstruct.d); \ */ static inline void dheap_pop_swap_last_and_sift_down(dheap_t *heap, long last_index) { if (last_index == 0) { DHEAP_DROP_LAST(heap); } else { VALUE sift_value = DHEAP_VALUE(heap, last_index); VALUE sift_score = DHEAP_SCORE(heap, last_index); DHEAP_ASSIGN(heap, 0, sift_score, sift_value); DHEAP_DROP_LAST(heap); dheap_ary_sift_down(heap, 0); } } /* * Returns the next value on the heap to be popped without popping it. * * Time complexity: O(1) (worst-case) * @return [Object] the next value to be popped without popping it. */ static VALUE dheap_peek(VALUE self) { dheap_t *heap = get_dheap_struct(self); if (DHEAP_IDX_LAST(heap) < 0) return Qnil; return DHEAP_VALUE(heap, 0); } /* * Pops the minimum value from the top of the heap * * Time complexity: O(d log n / log d) (worst-case) */ static VALUE dheap_pop(VALUE self) { dheap_t *heap = get_dheap_struct(self); long last_index = DHEAP_IDX_LAST(heap); if (last_index < 0) return Qnil; VALUE pop_value = DHEAP_VALUE(heap, 0); dheap_pop_swap_last_and_sift_down(heap, last_index); return pop_value; } /* * Pops the minimum value only if it is less than or equal to a max score. * * @param max_score [#<=>] the maximum score to be popped * * @see #pop */ static VALUE dheap_pop_lte(VALUE self, VALUE max_score) { dheap_t *heap = get_dheap_struct(self); long last_index = DHEAP_IDX_LAST(heap); if (last_index < 0) return Qnil; VALUE pop_value = DHEAP_VALUE(heap, 0); VALUE pop_score = DHEAP_SCORE(heap, 0); if (max_score && !CMP_LTE(pop_score, max_score)) return Qnil; dheap_pop_swap_last_and_sift_down(heap, last_index); return pop_value; } /* * Pops the minimum value only if it is less than a max score. * * @param max_score [#<=>] the maximum score to be popped * * Time complexity: O(d log n / log d) (worst-case) */ static VALUE dheap_pop_lt(VALUE self, VALUE max_score) { dheap_t *heap = get_dheap_struct(self); long last_index = DHEAP_IDX_LAST(heap); if (last_index < 0) return Qnil; VALUE pop_value = DHEAP_VALUE(heap, 0); VALUE pop_score = DHEAP_SCORE(heap, 0); if (max_score && !CMP_LT(pop_score, max_score)) return Qnil; dheap_pop_swap_last_and_sift_down(heap, last_index); return pop_value; } void Init_d_heap(void) { id_cmp = rb_intern_const("<=>"); id_ivar_values = rb_intern_const("values"); id_ivar_scores = rb_intern_const("scores"); id_ivar_d = rb_intern_const("d"); rb_cDHeap = rb_define_class("DHeap", rb_cObject); rb_define_alloc_func(rb_cDHeap, dheap_s_alloc); rb_define_const(rb_cDHeap, "MAX_D", INT2NUM(DHEAP_MAX_D)); rb_define_const(rb_cDHeap, "DEFAULT_D", INT2NUM(DHEAP_DEFAULT_D)); rb_define_method(rb_cDHeap, "initialize", dheap_initialize, -1); rb_define_method(rb_cDHeap, "d", dheap_attr_d, 0); rb_define_method(rb_cDHeap, "freeze", dheap_freeze, 0); rb_define_method(rb_cDHeap, "size", dheap_size, 0); rb_define_method(rb_cDHeap, "empty?", dheap_empty_p, 0); rb_define_method(rb_cDHeap, "peek", dheap_peek, 0); rb_define_method(rb_cDHeap, "push", dheap_push, -1); rb_define_method(rb_cDHeap, "<<", dheap_left_shift, 1); rb_define_method(rb_cDHeap, "pop", dheap_pop, 0); rb_define_method(rb_cDHeap, "pop_lt", dheap_pop_lt, 1); rb_define_method(rb_cDHeap, "pop_lte", dheap_pop_lte, 1); }