From 3a83c798f7a466fd84605235052c4c221fceefd0 Mon Sep 17 00:00:00 2001 From: Alexander Lopez Date: Thu, 21 Nov 2024 09:31:40 -0800 Subject: [PATCH 1/6] add outline for priority queue closures for inc, dec, and update --- ccc/impl/impl_priority_queue.h | 45 ++++++++++++++ ccc/priority_queue.h | 9 +++ samples/graph.c | 14 +---- src/priority_queue.c | 105 ++++++++++++++++++++++----------- 4 files changed, 129 insertions(+), 44 deletions(-) diff --git a/ccc/impl/impl_priority_queue.h b/ccc/impl/impl_priority_queue.h index 28a2a88e..1c9b7768 100644 --- a/ccc/impl/impl_priority_queue.h +++ b/ccc/impl/impl_priority_queue.h @@ -44,6 +44,9 @@ struct ccc_pq_ void ccc_impl_pq_push(struct ccc_pq_ *, struct ccc_pq_elem_ *); struct ccc_pq_elem_ *ccc_impl_pq_elem_in(struct ccc_pq_ const *, void const *); +void ccc_impl_pq_update_fixup(struct ccc_pq_ *, struct ccc_pq_elem_ *); +void ccc_impl_pq_increase_fixup(struct ccc_pq_ *, struct ccc_pq_elem_ *); +void ccc_impl_pq_decrease_fixup(struct ccc_pq_ *, struct ccc_pq_elem_ *); #define ccc_impl_pq_emplace(pq_ptr, lazy_value...) \ (__extension__({ \ @@ -69,4 +72,46 @@ struct ccc_pq_elem_ *ccc_impl_pq_elem_in(struct ccc_pq_ const *, void const *); pq_res_; \ })) +#define ccc_impl_pq_update_w(pq_ptr, pq_elem_ptr, update_closure_over_T) \ + (__extension__({ \ + struct ccc_pq_ *const pq_ = (pq_ptr); \ + bool pq_update_res_ = false; \ + struct ccc_pq_elem_ *const pq_elem_ptr_ = (pq_elem_ptr); \ + if (pq_ && pq_elem_ptr_) \ + { \ + pq_update_res_ = true; \ + {update_closure_over_T} ccc_impl_pq_update_fixup(pq_, \ + pq_elem_ptr_); \ + } \ + pq_update_res_; \ + })) + +#define ccc_impl_pq_increase_w(pq_ptr, pq_elem_ptr, increase_closure_over_T) \ + (__extension__({ \ + struct ccc_pq_ *const pq_ = (pq_ptr); \ + bool pq_increase_res_ = false; \ + struct ccc_pq_elem_ *const pq_elem_ptr_ = (pq_elem_ptr); \ + if (pq_ && pq_elem_ptr_) \ + { \ + pq_increase_res_ = true; \ + {increase_closure_over_T} ccc_impl_pq_increase_fixup( \ + pq_, pq_elem_ptr_); \ + } \ + pq_increase_res_; \ + })) + +#define ccc_impl_pq_decrease_w(pq_ptr, pq_elem_ptr, decrease_closure_over_T) \ + (__extension__({ \ + struct ccc_pq_ *const pq_ = (pq_ptr); \ + bool pq_decrease_res_ = false; \ + struct ccc_pq_elem_ *const pq_elem_ptr_ = (pq_elem_ptr); \ + if (pq_ && pq_elem_ptr_) \ + { \ + pq_decrease_res_ = true; \ + {decrease_closure_over_T} ccc_impl_pq_decrease_fixup( \ + pq_, pq_elem_ptr_); \ + } \ + pq_decrease_res_; \ + })) + #endif /* CCC_IMPL_PRIORITY_QUEUE_H */ diff --git a/ccc/priority_queue.h b/ccc/priority_queue.h index a51793f7..b4ebf78b 100644 --- a/ccc/priority_queue.h +++ b/ccc/priority_queue.h @@ -124,6 +124,9 @@ operations. O(1) best case, O(lgN) worst case. */ bool ccc_pq_update(ccc_priority_queue *pq, ccc_pq_elem *elem, ccc_update_fn *fn, void *aux); +#define ccc_pq_update_w(pq_ptr, pq_elem_ptr, update_closure_over_T) \ + ccc_impl_pq_update_w(pq_ptr, pq_elem_ptr, update_closure_over_T) + /** @brief Increases the value of the type wrapping elem. O(1) or O(lgN) @param [in] pq a pointer to the priority queue. @param [in] elem a pointer to the intrusive element in the user type. @@ -141,6 +144,9 @@ from the pq creates an amortized o(lgN) runtime for this function. */ bool ccc_pq_increase(ccc_priority_queue *pq, ccc_pq_elem *elem, ccc_update_fn *fn, void *aux); +#define ccc_pq_increase_w(pq_ptr, pq_elem_ptr, increase_closure_over_T) \ + ccc_impl_pq_increase_w(pq_ptr, pq_elem_ptr, increase_closure_over_T) + /** @brief Decreases the value of the type wrapping elem. O(1) or O(lgN) @param [in] pq a pointer to the priority queue. @param [in] elem a pointer to the intrusive element in the user type. @@ -158,6 +164,9 @@ from the pq creates an amortized o(lgN) runtime for this function. */ bool ccc_pq_decrease(ccc_priority_queue *pq, ccc_pq_elem *elem, ccc_update_fn *fn, void *aux); +#define ccc_pq_decrease_w(pq_ptr, pq_elem_ptr, decrease_closure_over_T) \ + ccc_impl_pq_decrease_w(pq_ptr, pq_elem_ptr, decrease_closure_over_T) + /**@}*/ /** @name Deallocation Interface diff --git a/samples/graph.c b/samples/graph.c index bf8ed267..846cb191 100644 --- a/samples/graph.c +++ b/samples/graph.c @@ -292,7 +292,6 @@ static bool eq_parent_cells(ccc_key_cmp); static uint64_t hash_parent_cells(ccc_user_key point_struct); static uint64_t hash_64_bits(uint64_t); -static void pq_update_dist(ccc_user_type); static unsigned count_digits(uintmax_t n); /*====================== Main Arg Handling ===============================*/ @@ -773,10 +772,9 @@ dijkstra_shortest_path(struct graph *const graph, struct path_request const pr) /* Build the map with the appropriate best candidate parent. */ next->prev_name = cur->cur_name; /* Dijkstra with update technique tests the pq abilities. */ - if (!decrease(&costs_pq, &next->pq_elem, pq_update_dist, &alt)) - { - quit("Updating vertex that is not in queue.\n", 1); - } + bool const relax_res = ccc_pq_decrease_w( + &costs_pq, &next->pq_elem, { next->dist = alt; }); + prog_assert(relax_res == true); } } } @@ -1148,12 +1146,6 @@ hash_64_bits(uint64_t x) return x; } -static void -pq_update_dist(ccc_user_type const u) -{ - ((struct dijkstra_vertex *)u.user_type)->dist = *((int *)u.aux); -} - /*=========================== Misc ====================================*/ static struct path_request diff --git a/src/priority_queue.c b/src/priority_queue.c index 17720544..ba354758 100644 --- a/src/priority_queue.c +++ b/src/priority_queue.c @@ -26,6 +26,9 @@ static void *elem_in(struct ccc_pq_ const *, struct ccc_pq_elem_ const *); static ccc_threeway_cmp cmp(struct ccc_pq_ const *, struct ccc_pq_elem_ const *a, struct ccc_pq_elem_ const *b); +static void update_fixup(struct ccc_pq_ *, struct ccc_pq_elem_ *); +static void increase_fixup(struct ccc_pq_ *, struct ccc_pq_elem_ *); +static void decrease_fixup(struct ccc_pq_ *, struct ccc_pq_elem_ *); /*========================= Interface Functions ==========================*/ @@ -156,15 +159,7 @@ ccc_pq_update(ccc_priority_queue *const pq, ccc_pq_elem *const e, return false; } fn((ccc_user_type){struct_base(pq, e), aux}); - if (e->parent_ && cmp(pq, e, e->parent_) == pq->order_) - { - cut_child(e); - pq->root_ = merge(pq, pq->root_, e); - return true; - } - pq->root_ = delete_node(pq, e); - init_node(e); - pq->root_ = merge(pq, pq->root_, e); + update_fixup(pq, e); return true; } @@ -178,18 +173,8 @@ ccc_pq_increase(ccc_priority_queue *const pq, ccc_pq_elem *const e, { return false; } - if (pq->order_ == CCC_GRT) - { - fn((ccc_user_type){struct_base(pq, e), aux}); - cut_child(e); - } - else - { - pq->root_ = delete_node(pq, e); - fn((ccc_user_type){struct_base(pq, e), aux}); - init_node(e); - } - pq->root_ = merge(pq, pq->root_, e); + fn((ccc_user_type){struct_base(pq, e), aux}); + increase_fixup(pq, e); return true; } @@ -203,18 +188,8 @@ ccc_pq_decrease(ccc_priority_queue *const pq, ccc_pq_elem *const e, { return false; } - if (pq->order_ == CCC_LES) - { - fn((ccc_user_type){struct_base(pq, e), aux}); - cut_child(e); - } - else - { - pq->root_ = delete_node(pq, e); - fn((ccc_user_type){struct_base(pq, e), aux}); - init_node(e); - } - pq->root_ = merge(pq, pq->root_, e); + fn((ccc_user_type){struct_base(pq, e), aux}); + decrease_fixup(pq, e); return true; } @@ -257,8 +232,72 @@ ccc_impl_pq_elem_in(struct ccc_pq_ const *const pq, return elem_in(pq, user_struct); } +void +ccc_impl_pq_update_fixup(struct ccc_pq_ *const pq, struct ccc_pq_elem_ *const e) +{ + return update_fixup(pq, e); +} + +void +ccc_impl_pq_increase_fixup(struct ccc_pq_ *const pq, + struct ccc_pq_elem_ *const e) +{ + return increase_fixup(pq, e); +} + +void +ccc_impl_pq_decrease_fixup(struct ccc_pq_ *const pq, + struct ccc_pq_elem_ *const e) +{ + return decrease_fixup(pq, e); +} + /*======================== Static Helpers ================================*/ +static inline void +update_fixup(struct ccc_pq_ *const pq, struct ccc_pq_elem_ *const e) +{ + if (e->parent_ && cmp(pq, e, e->parent_) == pq->order_) + { + cut_child(e); + pq->root_ = merge(pq, pq->root_, e); + return; + } + pq->root_ = delete_node(pq, e); + init_node(e); + pq->root_ = merge(pq, pq->root_, e); +} + +static inline void +increase_fixup(struct ccc_pq_ *const pq, struct ccc_pq_elem_ *const e) +{ + if (pq->order_ == CCC_GRT) + { + cut_child(e); + } + else + { + pq->root_ = delete_node(pq, e); + init_node(e); + } + pq->root_ = merge(pq, pq->root_, e); +} + +static inline void +decrease_fixup(struct ccc_pq_ *const pq, struct ccc_pq_elem_ *const e) +{ + if (pq->order_ == CCC_LES) + { + cut_child(e); + } + else + { + pq->root_ = delete_node(pq, e); + init_node(e); + } + pq->root_ = merge(pq, pq->root_, e); +} + static inline ccc_threeway_cmp cmp(struct ccc_pq_ const *const pq, struct ccc_pq_elem_ const *const a, struct ccc_pq_elem_ const *const b) From 10f886975a5e2ef107550aaf87d6f17b47e3149a Mon Sep 17 00:00:00 2001 From: Alexander Lopez Date: Thu, 21 Nov 2024 10:03:47 -0800 Subject: [PATCH 2/6] pq update docs done --- ccc/impl/impl_priority_queue.h | 13 +++- ccc/priority_queue.h | 106 +++++++++++++++++++++++++++-- tests/pq/test_pq_update.c | 118 +++++++++++++++++++++++++++++++-- 3 files changed, 223 insertions(+), 14 deletions(-) diff --git a/ccc/impl/impl_priority_queue.h b/ccc/impl/impl_priority_queue.h index 1c9b7768..c8cf7767 100644 --- a/ccc/impl/impl_priority_queue.h +++ b/ccc/impl/impl_priority_queue.h @@ -29,6 +29,8 @@ struct ccc_pq_ void *aux_; }; +/* NOLINTBEGIN(readability-identifier-naming) */ + #define ccc_impl_pq_init(struct_name, pq_elem_field, pq_order, alloc_fn, \ cmp_fn, aux_data) \ { \ @@ -77,7 +79,8 @@ void ccc_impl_pq_decrease_fixup(struct ccc_pq_ *, struct ccc_pq_elem_ *); struct ccc_pq_ *const pq_ = (pq_ptr); \ bool pq_update_res_ = false; \ struct ccc_pq_elem_ *const pq_elem_ptr_ = (pq_elem_ptr); \ - if (pq_ && pq_elem_ptr_) \ + if (pq_ && pq_elem_ptr_ && pq_elem_ptr_->next_sibling_ \ + && pq_elem_ptr_->prev_sibling_) \ { \ pq_update_res_ = true; \ {update_closure_over_T} ccc_impl_pq_update_fixup(pq_, \ @@ -91,7 +94,8 @@ void ccc_impl_pq_decrease_fixup(struct ccc_pq_ *, struct ccc_pq_elem_ *); struct ccc_pq_ *const pq_ = (pq_ptr); \ bool pq_increase_res_ = false; \ struct ccc_pq_elem_ *const pq_elem_ptr_ = (pq_elem_ptr); \ - if (pq_ && pq_elem_ptr_) \ + if (pq_ && pq_elem_ptr_ && pq_elem_ptr_->next_sibling_ \ + && pq_elem_ptr_->prev_sibling_) \ { \ pq_increase_res_ = true; \ {increase_closure_over_T} ccc_impl_pq_increase_fixup( \ @@ -105,7 +109,8 @@ void ccc_impl_pq_decrease_fixup(struct ccc_pq_ *, struct ccc_pq_elem_ *); struct ccc_pq_ *const pq_ = (pq_ptr); \ bool pq_decrease_res_ = false; \ struct ccc_pq_elem_ *const pq_elem_ptr_ = (pq_elem_ptr); \ - if (pq_ && pq_elem_ptr_) \ + if (pq_ && pq_elem_ptr_ && pq_elem_ptr_->next_sibling_ \ + && pq_elem_ptr_->prev_sibling_) \ { \ pq_decrease_res_ = true; \ {decrease_closure_over_T} ccc_impl_pq_decrease_fixup( \ @@ -114,4 +119,6 @@ void ccc_impl_pq_decrease_fixup(struct ccc_pq_ *, struct ccc_pq_elem_ *); pq_decrease_res_; \ })) +/* NOLINTEND(readability-identifier-naming) */ + #endif /* CCC_IMPL_PRIORITY_QUEUE_H */ diff --git a/ccc/priority_queue.h b/ccc/priority_queue.h index b4ebf78b..508f2206 100644 --- a/ccc/priority_queue.h +++ b/ccc/priority_queue.h @@ -109,31 +109,60 @@ pq is empty. Note that the user must ensure that elem is in the priority queue. */ ccc_result ccc_pq_erase(ccc_priority_queue *pq, ccc_pq_elem *elem); -/** @brief Update the value in the user type wrapping elem. +/** @brief Update the priority in the user type wrapping elem. @param [in] pq a pointer to the priority queue. @param [in] elem a pointer to the intrusive element in the user type. @param [in] fn the update function to act on the type wrapping elem. @param [in] aux any auxiliary data needed for the update function. -@return true if the update occured false if parameters were invalid or the +@return true if the update occurred false if parameters were invalid or the function can deduce elem is not in the pq. @warning the user must ensure elem is in the pq. -Note that this operation may incur uneccessary overhead if the user can't -deduce if an increase or decrease is occuring. See the increase and decrease +Note that this operation may incur unnecessary overhead if the user can't +deduce if an increase or decrease is occurring. See the increase and decrease operations. O(1) best case, O(lgN) worst case. */ bool ccc_pq_update(ccc_priority_queue *pq, ccc_pq_elem *elem, ccc_update_fn *fn, void *aux); +/** @brief Update the priority in the user type stored in the container. +@param [in] pq_ptr a pointer to the priority queue. +@param [in] pq_elem_ptr a pointer to the intrusive handle in the user type. +@param [in] update_closure_over_T the semicolon separated statements to execute +on the user type which wraps pq_elem_ptr (optionally wrapping {code here} in +braces may help with formatting). This closure may safely modify the key used to +track the user element's priority in the priority queue. +@return true if the update occurred false if parameters were invalid or the +function can deduce elem is not in the pq. +@warning the user must ensure elem is in the pq and pq_elem_ptr points to the +intrusive element in the same user type that is being updated. + +``` +#define PRIORITY_QUEUE_USING_NAMESPACE_CCC +struct val +{ + pq_elem e; + int key; +}; +priority_queue pq = build_rand_pq(); +struct val *i = get_rand_pq_elem(&pq); +pq_update_w(&pq, &i->e, { i->key = rand_key(); }); +``` + +Note that this operation may incur unnecessary overhead if the user can't +deduce if an increase or decrease is occurring. See the increase and decrease +operations. O(1) best case, O(lgN) worst case. */ #define ccc_pq_update_w(pq_ptr, pq_elem_ptr, update_closure_over_T) \ ccc_impl_pq_update_w(pq_ptr, pq_elem_ptr, update_closure_over_T) -/** @brief Increases the value of the type wrapping elem. O(1) or O(lgN) +/** @brief Increases the priority of the type wrapping elem. O(1) or O(lgN) @param [in] pq a pointer to the priority queue. @param [in] elem a pointer to the intrusive element in the user type. @param [in] fn the update function to act on the type wrapping elem. @param [in] aux any auxiliary data needed for the update function. -@return true if the increase occured false if parameters were invalid or the +@return true if the increase occurred false if parameters were invalid or the function can deduce elem is not in the pq. +@warning the data structure will be in an invalid state if the user decreases +the priority by mistake in this function. Note that this is optimal update technique if the priority queue has been initialized as a max queue and the new value is known to be greater than the old @@ -144,6 +173,38 @@ from the pq creates an amortized o(lgN) runtime for this function. */ bool ccc_pq_increase(ccc_priority_queue *pq, ccc_pq_elem *elem, ccc_update_fn *fn, void *aux); +/** @brief Increases the priority of the user type stored in the container. +@param [in] pq_ptr a pointer to the priority queue. +@param [in] pq_elem_ptr a pointer to the intrusive handle in the user type. +@param [in] increase_closure_over_T the semicolon separated statements to +execute on the user type which wraps pq_elem_ptr (optionally wrapping {code +here} in braces may help with formatting). This closure may safely increase the +key used to track the user element's priority in the priority queue. +@return true if the increase occurred false if parameters were invalid or the +function can deduce elem is not in the pq. +@warning the user must ensure elem is in the pq and pq_elem_ptr points to the +intrusive element in the same user type that is being increased. The data +structure will be in an invalid state if the user decreases the priority by +mistake in this function. + +``` +#define PRIORITY_QUEUE_USING_NAMESPACE_CCC +struct val +{ + pq_elem e; + int key; +}; +priority_queue pq = build_rand_pq(); +struct val *i = get_rand_pq_elem(&pq); +pq_increase_w(&pq, &i->e, { i->key++; }); +``` + +Note that this is optimal update technique if the priority queue has been +initialized as a max queue and the new value is known to be greater than the old +value. If this is a max heap O(1), otherwise O(lgN). + +While the best case operation is O(1) the impact of restructuring on future pops +from the pq creates an amortized o(lgN) runtime for this function. */ #define ccc_pq_increase_w(pq_ptr, pq_elem_ptr, increase_closure_over_T) \ ccc_impl_pq_increase_w(pq_ptr, pq_elem_ptr, increase_closure_over_T) @@ -152,7 +213,7 @@ bool ccc_pq_increase(ccc_priority_queue *pq, ccc_pq_elem *elem, @param [in] elem a pointer to the intrusive element in the user type. @param [in] fn the update function to act on the type wrapping elem. @param [in] aux any auxiliary data needed for the update function. -@return true if the decrease occured false if parameters were invalid or the +@return true if the decrease occurred false if parameters were invalid or the function can deduce elem is not in the pq. Note that this is optimal update technique if the priority queue has been @@ -164,6 +225,37 @@ from the pq creates an amortized o(lgN) runtime for this function. */ bool ccc_pq_decrease(ccc_priority_queue *pq, ccc_pq_elem *elem, ccc_update_fn *fn, void *aux); +/** @brief Decreases the priority of the user type stored in the container. +@param [in] pq_ptr a pointer to the priority queue. +@param [in] pq_elem_ptr a pointer to the intrusive handle in the user type. +@param [in] decrease_closure_over_T the semicolon separated statements to +execute on the user type which wraps pq_elem_ptr (optionally wrapping {code +here} in braces may help with formatting). This closure may safely decrease the +key used to track the user element's priority in the priority queue. +@return true if the decrease occurred false if parameters were invalid or the +function can deduce elem is not in the pq. +@warning the user must ensure elem is in the pq and pq_elem_ptr points to the +intrusive element in the same user type that is being decreased. The data +structure will be in an invalid state if the user decreases the priority by +mistake in this function. + +``` +#define PRIORITY_QUEUE_USING_NAMESPACE_CCC +struct val +{ + pq_elem e; + int key; +}; +priority_queue pq = build_rand_pq(); +struct val *i = get_rand_pq_elem(&pq); +pq_decrease_w(&pq, &i->e, { i->key--; }); +``` +Note that this is optimal update technique if the priority queue has been +initialized as a min queue and the new value is known to be less than the old +value. If this is a min heap O(1), otherwise O(lgN). + +While the best case operation is O(1) the impact of restructuring on future pops +from the pq creates an amortized o(lgN) runtime for this function. */ #define ccc_pq_decrease_w(pq_ptr, pq_elem_ptr, decrease_closure_over_T) \ ccc_impl_pq_decrease_w(pq_ptr, pq_elem_ptr, decrease_closure_over_T) diff --git a/tests/pq/test_pq_update.c b/tests/pq/test_pq_update.c index cedffba6..d8c48f11 100644 --- a/tests/pq/test_pq_update.c +++ b/tests/pq/test_pq_update.c @@ -102,6 +102,38 @@ CHECK_BEGIN_STATIC_FN(pq_test_priority_update) CHECK_END_FN(); } +CHECK_BEGIN_STATIC_FN(pq_test_priority_update_with) +{ + ccc_priority_queue pq + = ccc_pq_init(struct val, elem, CCC_LES, NULL, val_cmp, NULL); + /* Seed the test with any integer for reproducible random test sequence + currently this will change every test. NOLINTNEXTLINE */ + srand(time(NULL)); + size_t const num_nodes = 1000; + struct val vals[1000]; + for (size_t i = 0; i < num_nodes; ++i) + { + /* Force duplicates. */ + vals[i].val = rand() % (num_nodes + 1); // NOLINT + vals[i].id = (int)i; + CHECK(push(&pq, &vals[i].elem) != NULL, true); + CHECK(validate(&pq), true); + } + int const limit = 400; + for (size_t val = 0; val < num_nodes; ++val) + { + struct val *i = &vals[val]; + int backoff = i->val / 2; + if (i->val > limit) + { + CHECK(ccc_pq_update_w(&pq, &i->elem, { i->val = backoff; }), true); + CHECK(validate(&pq), true); + } + } + CHECK(ccc_pq_size(&pq), num_nodes); + CHECK_END_FN(); +} + CHECK_BEGIN_STATIC_FN(pq_test_priority_increase) { ccc_priority_queue pq @@ -125,7 +157,7 @@ CHECK_BEGIN_STATIC_FN(pq_test_priority_increase) struct val *const i = &vals[val]; int inc = limit * 2; int dec = i->val / 2; - if (i->val > limit) + if (i->val >= limit) { CHECK(ccc_pq_decrease(&pq, &i->elem, val_update, &dec), true); CHECK(validate(&pq), true); @@ -140,6 +172,44 @@ CHECK_BEGIN_STATIC_FN(pq_test_priority_increase) CHECK_END_FN(); } +CHECK_BEGIN_STATIC_FN(pq_test_priority_increase_with) +{ + ccc_priority_queue pq + = ccc_pq_init(struct val, elem, CCC_LES, NULL, val_cmp, NULL); + /* Seed the test with any integer for reproducible random test sequence + currently this will change every test. NOLINTNEXTLINE */ + srand(time(NULL)); + size_t const num_nodes = 1000; + struct val vals[1000]; + for (size_t i = 0; i < num_nodes; ++i) + { + /* Force duplicates. */ + vals[i].val = rand() % (num_nodes + 1); // NOLINT + vals[i].id = (int)i; + CHECK(push(&pq, &vals[i].elem) != NULL, true); + CHECK(validate(&pq), true); + } + int const limit = 400; + for (size_t val = 0; val < num_nodes; ++val) + { + struct val *const i = &vals[val]; + int inc = limit * 2; + int dec = i->val / 2; + if (i->val >= limit) + { + CHECK(ccc_pq_decrease_w(&pq, &i->elem, { i->val = dec; }), true); + CHECK(validate(&pq), true); + } + else + { + CHECK(ccc_pq_increase_w(&pq, &i->elem, { i->val = inc; }), true); + CHECK(validate(&pq), true); + } + } + CHECK(ccc_pq_size(&pq), num_nodes); + CHECK_END_FN(); +} + CHECK_BEGIN_STATIC_FN(pq_test_priority_decrease) { ccc_priority_queue pq @@ -178,10 +248,50 @@ CHECK_BEGIN_STATIC_FN(pq_test_priority_decrease) CHECK_END_FN(); } +CHECK_BEGIN_STATIC_FN(pq_test_priority_decrease_with) +{ + ccc_priority_queue pq + = ccc_pq_init(struct val, elem, CCC_GRT, NULL, val_cmp, NULL); + /* Seed the test with any integer for reproducible random test sequence + currently this will change every test. NOLINTNEXTLINE */ + srand(time(NULL)); + size_t const num_nodes = 1000; + struct val vals[1000]; + for (size_t i = 0; i < num_nodes; ++i) + { + /* Force duplicates. */ + vals[i].val = rand() % (num_nodes + 1); // NOLINT + vals[i].id = (int)i; + CHECK(push(&pq, &vals[i].elem) != NULL, true); + CHECK(validate(&pq), true); + } + int const limit = 400; + for (size_t val = 0; val < num_nodes; ++val) + { + struct val *const i = &vals[val]; + int inc = limit * 2; + int dec = i->val / 2; + if (i->val < limit) + { + CHECK(ccc_pq_increase_w(&pq, &i->elem, { i->val = inc; }), true); + CHECK(validate(&pq), true); + } + else + { + CHECK(ccc_pq_decrease_w(&pq, &i->elem, { i->val = dec; }), true); + CHECK(validate(&pq), true); + } + } + CHECK(ccc_pq_size(&pq), num_nodes); + CHECK_END_FN(); +} + int main() { - return CHECK_RUN(pq_test_insert_iterate_pop(), pq_test_priority_update(), - pq_test_priority_removal(), pq_test_priority_increase(), - pq_test_priority_decrease()); + return CHECK_RUN( + pq_test_insert_iterate_pop(), pq_test_priority_update(), + pq_test_priority_update_with(), pq_test_priority_removal(), + pq_test_priority_increase(), pq_test_priority_increase_with(), + pq_test_priority_decrease(), pq_test_priority_decrease_with()); } From 1b58146cf89420e500f1862104d64a8faf784024 Mon Sep 17 00:00:00 2001 From: Alexander Lopez Date: Thu, 21 Nov 2024 10:28:23 -0800 Subject: [PATCH 3/6] fpq closures added --- ccc/flat_priority_queue.h | 66 ++++++++++++++++++++++ ccc/impl/impl_flat_priority_queue.h | 21 +++++++ ccc/priority_queue.h | 3 + src/flat_priority_queue.c | 85 +++++++++++++++++------------ tests/fpq/test_fpq_update.c | 36 ++++++++++++ 5 files changed, 175 insertions(+), 36 deletions(-) diff --git a/ccc/flat_priority_queue.h b/ccc/flat_priority_queue.h index bdf28e24..a85fa5eb 100644 --- a/ccc/flat_priority_queue.h +++ b/ccc/flat_priority_queue.h @@ -136,6 +136,27 @@ ccc_result ccc_fpq_erase(ccc_flat_priority_queue *fpq, void *e); bool ccc_fpq_update(ccc_flat_priority_queue *fpq, void *e, ccc_update_fn *fn, void *aux); +/** @brief Update the user type stored in the priority queue directly. O(lgN). +@param [in] fpq_ptr a pointer to the flat priority queue. +@param [in] T_ptr a pointer to the user type being updated. +@param [in] update_closure_over_T the semicolon separated statements to execute +on the user type at T_ptr (optionally wrapping {code here} in braces may help +with formatting). This closure may safely modify the key used to track the user +element's priority in the priority queue. +@return true on success, false if parameters are invalid or fpq is empty. +@warning the user must ensure T_ptr is in the fpq. + +``` +#define FLAT_PRIORITY_QUEUE_USING_NAMESPACE_CCC +flat_priority_queue fpq = build_rand_int_fpq(); +int *i = get_rand_fpq_elem(&fpq); +fpq_update_w(&fpq, i, { *i = rand_key(); }); +``` + +Note that whether the key increases or decreases does not affect runtime. */ +#define ccc_fpq_update_w(fpq_ptr, T_ptr, update_closure_over_T) \ + ccc_impl_fpq_update_w(fpq_ptr, T_ptr, update_closure_over_T) + /** @brief Increase e that is a handle to the stored fpq element. O(lgN). @param [in] fpq a pointer to the flat priority queue. @param [in] e a handle to the stored fpq element. Must be in the fpq. @@ -146,6 +167,27 @@ bool ccc_fpq_update(ccc_flat_priority_queue *fpq, void *e, ccc_update_fn *fn, bool ccc_fpq_increase(ccc_flat_priority_queue *fpq, void *e, ccc_update_fn *fn, void *aux); +/** @brief Increase the user type stored in the priority queue directly. O(lgN). +@param [in] fpq_ptr a pointer to the flat priority queue. +@param [in] T_ptr a pointer to the user type being updated. +@param [in] increase_closure_over_T the semicolon separated statements to +execute on the user type at T_ptr (optionally wrapping {code here} in braces may +help with formatting). This closure may safely modify the key used to track the +user element's priority in the priority queue. +@return true on success, false if parameters are invalid or fpq is empty. +@warning the user must ensure T_ptr is in the fpq. + +``` +#define FLAT_PRIORITY_QUEUE_USING_NAMESPACE_CCC +flat_priority_queue fpq = build_rand_int_fpq(); +int *i = get_rand_fpq_elem(&fpq); +fpq_update_w(&fpq, i, { (*i)++; }); +``` + +Note that if this priority queue is min or max, the runtime is the same. */ +#define ccc_fpq_increase_w(fpq_ptr, T_ptr, increase_closure_over_T) \ + ccc_impl_fpq_increase_w(fpq_ptr, T_ptr, increase_closure_over_T) + /** @brief Decrease e that is a handle to the stored fpq element. O(lgN). @param [in] fpq a pointer to the flat priority queue. @param [in] e a handle to the stored fpq element. Must be in the fpq. @@ -156,6 +198,27 @@ bool ccc_fpq_increase(ccc_flat_priority_queue *fpq, void *e, ccc_update_fn *fn, bool ccc_fpq_decrease(ccc_flat_priority_queue *fpq, void *e, ccc_update_fn *fn, void *aux); +/** @brief Increase the user type stored in the priority queue directly. O(lgN). +@param [in] fpq_ptr a pointer to the flat priority queue. +@param [in] T_ptr a pointer to the user type being updated. +@param [in] decrease_closure_over_T the semicolon separated statements to +execute on the user type at T_ptr (optionally wrapping {code here} in braces may +help with formatting). This closure may safely modify the key used to track the +user element's priority in the priority queue. +@return true on success, false if parameters are invalid or fpq is empty. +@warning the user must ensure T_ptr is in the fpq. + +``` +#define FLAT_PRIORITY_QUEUE_USING_NAMESPACE_CCC +flat_priority_queue fpq = build_rand_int_fpq(); +int *i = get_rand_fpq_elem(&fpq); +fpq_update_w(&fpq, i, { (*i)--; }); +``` + +Note that if this priority queue is min or max, the runtime is the same. */ +#define ccc_fpq_decrease_w(fpq_ptr, T_ptr, decrease_closure_over_T) \ + ccc_impl_fpq_decrease_w(fpq_ptr, T_ptr, decrease_closure_over_T) + /**@}*/ /** @name Deallocation Interface @@ -241,6 +304,9 @@ typedef ccc_flat_priority_queue flat_priority_queue; # define fpq_update(args...) ccc_fpq_update(args) # define fpq_increase(args...) ccc_fpq_increase(args) # define fpq_decrease(args...) ccc_fpq_decrease(args) +# define fpq_update_w(args...) ccc_fpq_update_w(args) +# define fpq_increase_w(args...) ccc_fpq_increase_w(args) +# define fpq_decrease_w(args...) ccc_fpq_decrease_w(args) # define fpq_clear(args...) ccc_fpq_clear(args) # define fpq_clear_and_free(args...) ccc_fpq_clear_and_free(args) # define fpq_is_empty(args...) ccc_fpq_is_empty(args) diff --git a/ccc/impl/impl_flat_priority_queue.h b/ccc/impl/impl_flat_priority_queue.h index 9e40a8cc..ff56c07c 100644 --- a/ccc/impl/impl_flat_priority_queue.h +++ b/ccc/impl/impl_flat_priority_queue.h @@ -22,6 +22,7 @@ struct ccc_fpq_ size_t ccc_impl_fpq_bubble_up(struct ccc_fpq_ *, char[], size_t); void ccc_impl_fpq_in_place_heapify(struct ccc_fpq_ *, size_t n); +void ccc_impl_fpq_update_fixup(struct ccc_fpq_ *, void *); /* NOLINTBEGIN(readability-identifier-naming) */ @@ -86,6 +87,26 @@ void ccc_impl_fpq_in_place_heapify(struct ccc_fpq_ *, size_t n); fpq_res_; \ })) +#define ccc_impl_fpq_update_w(fpq_ptr, T_ptr, update_closure_over_T) \ + (__extension__({ \ + struct ccc_fpq_ *const fpq_ = (fpq_ptr); \ + bool fpq_update_res_ = false; \ + void *const fpq_t_ptr_ = (T_ptr); \ + if (fpq_ && fpq_t_ptr_ && !ccc_buf_is_empty(&fpq_->buf_)) \ + { \ + fpq_update_res_ = true; \ + {update_closure_over_T} ccc_impl_fpq_update_fixup(fpq_, \ + fpq_t_ptr_); \ + } \ + fpq_update_res_; \ + })) + +#define ccc_impl_fpq_increase_w(fpq_ptr, T_ptr, increase_closure_over_T) \ + ccc_impl_fpq_update_w(fpq_ptr, T_ptr, increase_closure_over_T) + +#define ccc_impl_fpq_decrease_w(fpq_ptr, T_ptr, decrease_closure_over_T) \ + ccc_impl_fpq_update_w(fpq_ptr, T_ptr, decrease_closure_over_T) + /* NOLINTEND(readability-identifier-naming) */ #endif /* CCC_IMPL_FLAT_PRIORITY_QUEUE_H */ diff --git a/ccc/priority_queue.h b/ccc/priority_queue.h index 508f2206..557bedca 100644 --- a/ccc/priority_queue.h +++ b/ccc/priority_queue.h @@ -329,6 +329,9 @@ typedef ccc_priority_queue priority_queue; # define pq_update(args...) ccc_pq_update(args) # define pq_increase(args...) ccc_pq_increase(args) # define pq_decrease(args...) ccc_pq_decrease(args) +# define pq_update_w(args...) ccc_pq_update_w(args) +# define pq_increase_w(args...) ccc_pq_increase_w(args) +# define pq_decrease_w(args...) ccc_pq_decrease_w(args) # define pq_order(args...) ccc_pq_order(args) # define pq_clear(args...) ccc_pq_clear(args) # define pq_validate(args...) ccc_pq_validate(args) diff --git a/src/flat_priority_queue.c b/src/flat_priority_queue.c index 0861d697..6d06cb6b 100644 --- a/src/flat_priority_queue.c +++ b/src/flat_priority_queue.c @@ -14,6 +14,7 @@ static bool wins(struct ccc_fpq_ const *, void const *winner, static void swap(struct ccc_fpq_ *, char tmp[], size_t, size_t); static size_t bubble_up(struct ccc_fpq_ *fpq, char tmp[], size_t i); static void bubble_down(struct ccc_fpq_ *, char tmp[], size_t); +static void update_fixup(struct ccc_fpq_ *, void *e); ccc_result ccc_fpq_alloc(ccc_flat_priority_queue *const fpq, size_t const new_capacity, @@ -26,21 +27,6 @@ ccc_fpq_alloc(ccc_flat_priority_queue *const fpq, size_t const new_capacity, return ccc_buf_alloc(&fpq->buf_, new_capacity, fn); } -void -ccc_impl_fpq_in_place_heapify(struct ccc_fpq_ *const fpq, size_t const n) -{ - if (!fpq || ccc_buf_capacity(&fpq->buf_) < n + 1) - { - return; - } - (void)ccc_buf_size_set(&fpq->buf_, n); - void *const tmp = ccc_buf_at(&fpq->buf_, n); - for (size_t i = (n / 2) + 1; i--;) - { - bubble_down(fpq, tmp, i); - } -} - ccc_result ccc_fpq_heapify(ccc_flat_priority_queue *const fpq, void *const array, size_t const n, size_t const input_elem_size) @@ -174,26 +160,7 @@ ccc_fpq_update(ccc_flat_priority_queue *const fpq, void *const e, return false; } fn((ccc_user_type){e, aux}); - void *tmp = ccc_buf_at(&fpq->buf_, ccc_buf_size(&fpq->buf_)); - size_t const i = index_of(fpq, e); - if (!i) - { - bubble_down(fpq, tmp, 0); - return true; - } - ccc_threeway_cmp const parent_cmp = fpq->cmp_( - (ccc_cmp){at(fpq, i), at(fpq, (i - 1) / 2), fpq->buf_.aux_}); - if (parent_cmp == fpq->order_) - { - (void)bubble_up(fpq, tmp, i); - return true; - } - if (parent_cmp != CCC_EQL) - { - bubble_down(fpq, tmp, i); - return true; - } - /* If the comparison is equal do nothing. Element is in right spot. */ + update_fixup(fpq, e); return true; } @@ -295,7 +262,7 @@ ccc_fpq_validate(ccc_flat_priority_queue const *const fpq) { void *const cur = at(fpq, i); /* Putting the child in the comparison function first evaluates - the childs three way comparison in relation to the parent. If + the child's three way comparison in relation to the parent. If the child beats the parent in total ordering (min/max) something has gone wrong. */ if (left < sz && wins(fpq, at(fpq, left), cur)) @@ -318,8 +285,54 @@ ccc_impl_fpq_bubble_up(struct ccc_fpq_ *const fpq, char tmp[], size_t i) return bubble_up(fpq, tmp, i); } +void +ccc_impl_fpq_update_fixup(struct ccc_fpq_ *const fpq, void *const e) +{ + update_fixup(fpq, e); +} + +void +ccc_impl_fpq_in_place_heapify(struct ccc_fpq_ *const fpq, size_t const n) +{ + if (!fpq || ccc_buf_capacity(&fpq->buf_) < n + 1) + { + return; + } + (void)ccc_buf_size_set(&fpq->buf_, n); + void *const tmp = ccc_buf_at(&fpq->buf_, n); + for (size_t i = (n / 2) + 1; i--;) + { + bubble_down(fpq, tmp, i); + } +} + /*=============================== Static Helpers =========================*/ +static inline void +update_fixup(struct ccc_fpq_ *const fpq, void *const e) +{ + void *const tmp = ccc_buf_at(&fpq->buf_, ccc_buf_size(&fpq->buf_)); + size_t const i = index_of(fpq, e); + if (!i) + { + bubble_down(fpq, tmp, 0); + return; + } + ccc_threeway_cmp const parent_cmp = fpq->cmp_( + (ccc_cmp){at(fpq, i), at(fpq, (i - 1) / 2), fpq->buf_.aux_}); + if (parent_cmp == fpq->order_) + { + (void)bubble_up(fpq, tmp, i); + return; + } + if (parent_cmp != CCC_EQL) + { + bubble_down(fpq, tmp, i); + return; + } + /* If the comparison is equal do nothing. Element is in right spot. */ +} + static inline size_t bubble_up(struct ccc_fpq_ *const fpq, char tmp[], size_t i) { diff --git a/tests/fpq/test_fpq_update.c b/tests/fpq/test_fpq_update.c index 014f52ec..53180286 100644 --- a/tests/fpq/test_fpq_update.c +++ b/tests/fpq/test_fpq_update.c @@ -109,9 +109,45 @@ CHECK_BEGIN_STATIC_FN(fpq_test_priority_update) CHECK_END_FN(); } +CHECK_BEGIN_STATIC_FN(fpq_test_priority_update_with) +{ + /* Seed the test with any integer for reproducible random test sequence + currently this will change every test. NOLINTNEXTLINE */ + srand(time(NULL)); + size_t const num_nodes = 1000; + struct val vals[1000 + 1]; + ccc_flat_priority_queue fpq = ccc_fpq_init( + vals, (sizeof(vals) / sizeof(vals[0])), CCC_LES, NULL, val_cmp, NULL); + for (size_t i = 0; i < num_nodes; ++i) + { + /* Force duplicates. */ + struct val const *res = ccc_fpq_emplace( + &fpq, (struct val){ + .val = rand() % (num_nodes + 1), /*NOLINT*/ + .id = (int)i, + }); + CHECK(res != NULL, true); + CHECK(validate(&fpq), true); + } + int const limit = 400; + for (size_t val = 0; val < num_nodes; ++val) + { + struct val *cur = &vals[val]; + int backoff = cur->val / 2; + if (cur->val > limit) + { + CHECK(ccc_fpq_update_w(&fpq, cur, { cur->val = backoff; }), true); + CHECK(validate(&fpq), true); + } + } + CHECK(size(&fpq), num_nodes); + CHECK_END_FN(); +} + int main() { return CHECK_RUN(fpq_test_insert_iterate_pop(), fpq_test_priority_update(), + fpq_test_priority_update_with(), fpq_test_priority_removal()); } From 308447682d6f5ac5a4bfcddb7cf262178de33f6c Mon Sep 17 00:00:00 2001 From: Alexander Lopez Date: Thu, 21 Nov 2024 10:35:20 -0800 Subject: [PATCH 4/6] add pq update closures to readme samples --- README.md | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index aa42718e..282bcb5a 100644 --- a/README.md +++ b/README.md @@ -304,6 +304,8 @@ main(void) capacity for swap space, min priority queue, no allocation, no aux. */ flat_priority_queue pq = fpq_heapify_init( heap, (sizeof(heap) / sizeof(int)), 19, CCC_LES, NULL, int_cmp, NULL); + int *elem = &heap[5]; + fpq_update_w(&pq, elem, { elem -= 4; }); int prev = *((int *)front(&pq)); (void)pop(&pq); while (!is_empty(&pq)) @@ -561,13 +563,6 @@ val_cmp(ccc_cmp const cmp) return (lhs->val > rhs->val) - (lhs->val < rhs->val); } -static void -decrease_val(ccc_user_type const t) -{ - struct val *const v = t.user_type; - v->val = *(int *)t.aux; -} - int main(void) { @@ -579,8 +574,8 @@ main(void) struct val const *const v = push(&pq, &elems[i].elem); assert(v && v->val == elems[i].val); } - int new_v = -99; - bool const decreased = decrease(&pq, &elems[4].elem, decrease_val, &new_v); + bool const decreased = pq_decrease_w(&pq, &elems[4].elem, + { elems[4].val = -99; }); assert(decreased); struct val const *const v = front(&pq); assert(v->val == -99); From 3198ab3ed286a83ff8fca35fada73a1e802fa9f7 Mon Sep 17 00:00:00 2001 From: Alexander Lopez Date: Thu, 21 Nov 2024 10:36:54 -0800 Subject: [PATCH 5/6] add helper comment for fpq --- ccc/impl/impl_flat_priority_queue.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ccc/impl/impl_flat_priority_queue.h b/ccc/impl/impl_flat_priority_queue.h index ff56c07c..4b0d05f9 100644 --- a/ccc/impl/impl_flat_priority_queue.h +++ b/ccc/impl/impl_flat_priority_queue.h @@ -87,6 +87,8 @@ void ccc_impl_fpq_update_fixup(struct ccc_fpq_ *, void *); fpq_res_; \ })) +/* Only one update fn is needed because there is no advantage to updates if + it is known they are min/max increase/decrease etc. */ #define ccc_impl_fpq_update_w(fpq_ptr, T_ptr, update_closure_over_T) \ (__extension__({ \ struct ccc_fpq_ *const fpq_ = (fpq_ptr); \ From 271a8fc311eec0adc0a3d05cecb8131269741ecd Mon Sep 17 00:00:00 2001 From: Alexander Lopez Date: Thu, 21 Nov 2024 10:42:47 -0800 Subject: [PATCH 6/6] fix readme sample and source --- README.md | 16 +++++++--------- samples/graph.c | 5 +++-- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 282bcb5a..c9c9dbae 100644 --- a/README.md +++ b/README.md @@ -884,19 +884,17 @@ Composing multiple containers with this approach is also possible. Consider the ```c struct dijkstra_vertex { - ccc_romap_elem path_elem; - ccc_pq_elem pq_elem; + romap_elem path_elem; + pq_elem pq_elem; int dist; char cur_name; char prev_name; }; /* ... Later Initialization After Memory is Prepared ... */ -ccc_realtime_ordered_map path_map = ccc_rom_init( - path_map, struct dijkstra_vertex, path_elem, cur_name, arena_alloc, - cmp_prev_vertices, &bump_arena); -ccc_priority_queue costs_pq - = ccc_pq_init(struct dijkstra_vertex, pq_elem, CCC_LES, NULL, - cmp_pq_costs, NULL); +realtime_ordered_map path_map = rom_init(path_map, struct dijkstra_vertex, + path_elem, cur_name, arena_alloc, cmp_prev_vertices, &bump_arena); +priority_queue costs_pq + = pq_init(struct dijkstra_vertex, pq_elem, LES, NULL, cmp_pq_costs, NULL); /*... Steps to Prepare to Run the Algorithm ...*/ while (!is_empty(&costs_pq)) { @@ -912,7 +910,7 @@ while (!is_empty(&costs_pq)) if (alt < next->dist) { next->prev_name = cur->cur_name; - decrease(&costs_pq, &next->pq_elem, pq_update_dist, &alt); + pq_decrease_w(&costs_pq, &next->pq_elem, { next->dist = alt; }); } } } diff --git a/samples/graph.c b/samples/graph.c index 846cb191..0c46e2b0 100644 --- a/samples/graph.c +++ b/samples/graph.c @@ -18,6 +18,7 @@ Enter 'q' to quit. */ #include "realtime_ordered_map.h" #define FLAT_HASH_MAP_USING_NAMESPACE_CCC #define FLAT_DOUBLE_ENDED_QUEUE_USING_NAMESPACE_CCC +#define PRIORITY_QUEUE_USING_NAMESPACE_CCC #define TRAITS_USING_NAMESPACE_CCC #include @@ -772,8 +773,8 @@ dijkstra_shortest_path(struct graph *const graph, struct path_request const pr) /* Build the map with the appropriate best candidate parent. */ next->prev_name = cur->cur_name; /* Dijkstra with update technique tests the pq abilities. */ - bool const relax_res = ccc_pq_decrease_w( - &costs_pq, &next->pq_elem, { next->dist = alt; }); + bool const relax_res = pq_decrease_w(&costs_pq, &next->pq_elem, + { next->dist = alt; }); prog_assert(relax_res == true); } }