From 6c74baeef3574efe8bd7760c08fee61f442c54c9 Mon Sep 17 00:00:00 2001 From: Siarhei Fedartsou Date: Fri, 21 Jun 2024 22:01:42 +0200 Subject: [PATCH] Add util::BinaryHeap --- include/util/binary_heap.hpp | 40 ++++++++++ include/util/static_rtree.hpp | 27 +++---- unit_tests/util/binary_heap.cpp | 132 ++++++++++++++++++++++++++++++++ 3 files changed, 183 insertions(+), 16 deletions(-) create mode 100644 include/util/binary_heap.hpp create mode 100644 unit_tests/util/binary_heap.cpp diff --git a/include/util/binary_heap.hpp b/include/util/binary_heap.hpp new file mode 100644 index 000000000..3189addc7 --- /dev/null +++ b/include/util/binary_heap.hpp @@ -0,0 +1,40 @@ +#pragma once +#include +#include +#include + +namespace osrm::util +{ + +// in its essence it is std::priority_queue, but with `clear` method +template class BinaryHeap +{ + public: + bool empty() const { return heap_.empty(); } + + const T &top() const + { + BOOST_ASSERT(!heap_.empty()); + return heap_.front(); + } + + void pop() + { + BOOST_ASSERT(!heap_.empty()); + std::pop_heap(heap_.begin(), heap_.end()); + heap_.pop_back(); + } + + template void emplace(Args &&...args) + { + heap_.emplace_back(std::forward(args)...); + std::push_heap(heap_.begin(), heap_.end()); + } + + void clear() { heap_.clear(); } + + private: + std::vector heap_; +}; + +} // namespace osrm::util \ No newline at end of file diff --git a/include/util/static_rtree.hpp b/include/util/static_rtree.hpp index 1fa20b293..bbb12efa5 100644 --- a/include/util/static_rtree.hpp +++ b/include/util/static_rtree.hpp @@ -4,6 +4,7 @@ #include "storage/tar_fwd.hpp" #include "osrm/coordinate.hpp" #include "util/bearing.hpp" +#include "util/binary_heap.hpp" #include "util/coordinate_calculation.hpp" #include "util/deallocating_vector.hpp" #include "util/exception.hpp" @@ -553,21 +554,17 @@ class StaticRTree auto projected_coordinate = web_mercator::fromWGS84(input_coordinate); Coordinate fixed_projected_coordinate{projected_coordinate}; - // we use std::vector with std::push_heap/std::pop_heap instead of - // std::priority_queue to be able to re-use allocated memory: std::priority_queue does not - // have `clear` method - static thread_local std::vector traversal_queue; + // we re-use queue for each query to avoid re-allocating memory + static thread_local util::BinaryHeap traversal_queue; traversal_queue.clear(); // initialize queue with root element - traversal_queue.emplace_back(QueryCandidate{0, TreeIndex{}}); - std::push_heap(traversal_queue.begin(), traversal_queue.end()); + traversal_queue.emplace(QueryCandidate{0, TreeIndex{}}); while (!traversal_queue.empty()) { - QueryCandidate current_query_node = traversal_queue.front(); - std::pop_heap(traversal_queue.begin(), traversal_queue.end()); - traversal_queue.pop_back(); + QueryCandidate current_query_node = traversal_queue.top(); + traversal_queue.pop(); const TreeIndex ¤t_tree_index = current_query_node.tree_index; if (!current_query_node.is_segment()) @@ -717,11 +714,10 @@ class StaticRTree BOOST_ASSERT(0. <= squared_distance); BOOST_ASSERT(i < std::numeric_limits::max()); - traversal_queue.emplace_back(QueryCandidate{squared_distance, - leaf_id, - static_cast(i), - Coordinate{projected_nearest}}); - std::push_heap(traversal_queue.begin(), traversal_queue.end()); + traversal_queue.emplace(QueryCandidate{squared_distance, + leaf_id, + static_cast(i), + Coordinate{projected_nearest}}); } } @@ -750,10 +746,9 @@ class StaticRTree child.minimum_bounding_rectangle.GetMinSquaredDist( fixed_projected_input_coordinate); - traversal_queue.emplace_back(QueryCandidate{ + traversal_queue.emplace(QueryCandidate{ squared_lower_bound_to_element, TreeIndex(parent.level + 1, child_index - m_tree_level_starts[parent.level + 1])}); - std::push_heap(traversal_queue.begin(), traversal_queue.end()); } } diff --git a/unit_tests/util/binary_heap.cpp b/unit_tests/util/binary_heap.cpp new file mode 100644 index 000000000..354fe7464 --- /dev/null +++ b/unit_tests/util/binary_heap.cpp @@ -0,0 +1,132 @@ +#include "util/binary_heap.hpp" + +#include +#include + +BOOST_AUTO_TEST_SUITE(binary_heap_test) + +BOOST_AUTO_TEST_CASE(empty_heap) +{ + osrm::util::BinaryHeap heap; + BOOST_CHECK(heap.empty()); +} + +BOOST_AUTO_TEST_CASE(push_and_top) +{ + osrm::util::BinaryHeap heap; + heap.emplace(5); + BOOST_CHECK_EQUAL(heap.top(), 5); + BOOST_CHECK(!heap.empty()); +} + +BOOST_AUTO_TEST_CASE(push_multiple_and_order) +{ + osrm::util::BinaryHeap heap; + heap.emplace(5); + heap.emplace(10); + heap.emplace(3); + heap.emplace(8); + BOOST_CHECK_EQUAL(heap.top(), 10); +} + +BOOST_AUTO_TEST_CASE(pop_and_order) +{ + osrm::util::BinaryHeap heap; + heap.emplace(5); + heap.emplace(10); + heap.emplace(3); + heap.emplace(8); + + BOOST_CHECK_EQUAL(heap.top(), 10); + heap.pop(); + BOOST_CHECK_EQUAL(heap.top(), 8); + heap.pop(); + BOOST_CHECK_EQUAL(heap.top(), 5); + heap.pop(); + BOOST_CHECK_EQUAL(heap.top(), 3); + heap.pop(); + BOOST_CHECK(heap.empty()); +} + +BOOST_AUTO_TEST_CASE(clear_heap) +{ + osrm::util::BinaryHeap heap; + heap.emplace(5); + heap.emplace(10); + heap.clear(); + BOOST_CHECK(heap.empty()); +} + +BOOST_AUTO_TEST_CASE(emplace_with_custom_type) +{ + struct CustomType + { + int value; + CustomType(int v) : value(v) {} + bool operator<(const CustomType &other) const { return value < other.value; } + }; + + osrm::util::BinaryHeap heap; + heap.emplace(5); + heap.emplace(10); + heap.emplace(3); + BOOST_CHECK_EQUAL(heap.top().value, 10); +} + +BOOST_AUTO_TEST_CASE(large_number_of_elements) +{ + osrm::util::BinaryHeap heap; + for (int i = 0; i < 1000; ++i) + { + heap.emplace(i); + } + BOOST_CHECK_EQUAL(heap.top(), 999); + + for (int i = 999; i >= 0; --i) + { + BOOST_CHECK_EQUAL(heap.top(), i); + heap.pop(); + } + BOOST_CHECK(heap.empty()); +} + +BOOST_AUTO_TEST_CASE(duplicate_values) +{ + osrm::util::BinaryHeap heap; + heap.emplace(5); + heap.emplace(5); + heap.emplace(5); + + BOOST_CHECK_EQUAL(heap.top(), 5); + heap.pop(); + BOOST_CHECK_EQUAL(heap.top(), 5); + heap.pop(); + BOOST_CHECK_EQUAL(heap.top(), 5); + heap.pop(); + BOOST_CHECK(heap.empty()); +} + +BOOST_AUTO_TEST_CASE(string_type) +{ + osrm::util::BinaryHeap heap; + heap.emplace("apple"); + heap.emplace("banana"); + heap.emplace("cherry"); + + BOOST_CHECK_EQUAL(heap.top(), "cherry"); + heap.pop(); + BOOST_CHECK_EQUAL(heap.top(), "banana"); + heap.pop(); + BOOST_CHECK_EQUAL(heap.top(), "apple"); +} + +BOOST_AUTO_TEST_CASE(emplace_after_clear) +{ + osrm::util::BinaryHeap heap; + heap.emplace(5); + heap.clear(); + heap.emplace(10); + BOOST_CHECK_EQUAL(heap.top(), 10); +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file