From ae0e6bc3d9afbab586a86e84b04e4bdf661adfc1 Mon Sep 17 00:00:00 2001 From: yut23 Date: Mon, 16 Dec 2024 20:45:13 -0500 Subject: [PATCH] graph_traversal: allow passing multiple sources to bfs() --- aoc_lib/src/graph_traversal.hpp | 88 ++++++++++++++++++++++++++++----- 1 file changed, 75 insertions(+), 13 deletions(-) diff --git a/aoc_lib/src/graph_traversal.hpp b/aoc_lib/src/graph_traversal.hpp index dd77dad..2a0f825 100644 --- a/aoc_lib/src/graph_traversal.hpp +++ b/aoc_lib/src/graph_traversal.hpp @@ -126,6 +126,10 @@ concept Heuristic = requires(Func heuristic, const Key &key) { template concept FuncPassed = !std::same_as; +template +concept AnySourceCollection = + util::concepts::any_iterable_collection; + } // namespace detail /** @@ -141,19 +145,21 @@ concept FuncPassed = !std::same_as; * visited as a tree). `use_seen` should only be set to false if the graph has * no cycles. * - * Returns the distance from the source to the first target found, or -1 if not - * found. + * Returns the distance from the source(s) to the first target found, or -1 if + * not found. */ -template ProcessNeighbors, detail::IsTarget IsTarget = detail::optional_func, detail::Visit Visit = detail::optional_func> -int bfs(const Key &source, ProcessNeighbors &&process_neighbors, +int bfs(const ASC &sources, ProcessNeighbors &&process_neighbors, IsTarget &&is_target, Visit &&visit) { static_assert(detail::FuncPassed || detail::FuncPassed, "is_target and visit must not both be defaulted"); using visit_ret_t = typename detail::visit_invoke_result::type; - detail::maybe_unordered_set queue = {source}; + detail::maybe_unordered_set queue = {std::begin(sources), + std::end(sources)}; detail::maybe_unordered_set next_queue{}; detail::maybe_unordered_set seen{}; @@ -189,6 +195,32 @@ int bfs(const Key &source, ProcessNeighbors &&process_neighbors, return -1; } +// specialization for std::initializer_list, so deduction of `bfs({source, +// source}, ...)` works +template ProcessNeighbors, + detail::IsTarget IsTarget = detail::optional_func, + detail::Visit Visit = detail::optional_func> +int bfs(const std::initializer_list &sources, + ProcessNeighbors &&process_neighbors, IsTarget &&is_target, + Visit &&visit) { + // explicitly specify the ASC template argument to avoid recursion + return bfs>( + sources, std::forward(process_neighbors), + std::forward(is_target), std::forward(visit)); +} + +template ProcessNeighbors, + detail::IsTarget IsTarget = detail::optional_func, + detail::Visit Visit = detail::optional_func> +int bfs(const Key &source, ProcessNeighbors &&process_neighbors, + IsTarget &&is_target, Visit &&visit) { + return bfs>( + {source}, std::forward(process_neighbors), + std::forward(is_target), std::forward(visit)); +} + /** * Generic BFS on an arbitrary graph, with no duplicate checking. * @@ -422,12 +454,10 @@ struct tarjan_entry { * Components are returned in topological order, along with a set of the * directed edges between the components. */ -template SourceCollection, +template ProcessNeighbors> std::pair>, std::set>> -tarjan_scc(const SourceCollection &sources, - ProcessNeighbors &&process_neighbors) { +tarjan_scc(const ASC &sources, ProcessNeighbors &&process_neighbors) { int index = 0; std::stack S{}; std::vector> components{}; @@ -517,12 +547,20 @@ tarjan_scc(const SourceCollection &sources, return {std::move(components), std::move(reversed_links)}; } +template ProcessNeighbors> +std::pair>, std::set>> +tarjan_scc(const std::initializer_list &sources, + ProcessNeighbors &&process_neighbors) { + return tarjan_scc>( + sources, std::forward(process_neighbors)); +} + template ProcessNeighbors> std::pair>, std::set>> tarjan_scc(const Key &source, ProcessNeighbors &&process_neighbors) { const std::initializer_list sources = {source}; - return tarjan_scc(sources, - std::forward(process_neighbors)); + return tarjan_scc(sources, + std::forward(process_neighbors)); } /** @@ -804,6 +842,8 @@ void _lint_helper_template( std::function visit_with_parent_bool, std::function get_distance, std::function heuristic) { + const std::vector sources_vec{source, source}; + bfs(source, process_neighbors, is_target, {}); bfs(source, process_neighbors, {}, visit); bfs(source, process_neighbors, {}, visit_bool); @@ -815,6 +855,28 @@ void _lint_helper_template( bfs(source, process_neighbors, is_target, visit); bfs(source, process_neighbors, is_target, visit_bool); + bfs({source, source}, process_neighbors, is_target, {}); + bfs({source, source}, process_neighbors, {}, visit); + bfs({source, source}, process_neighbors, {}, visit_bool); + bfs({source, source}, process_neighbors, is_target, visit); + bfs({source, source}, process_neighbors, is_target, visit_bool); + bfs({source, source}, process_neighbors, is_target, {}); + bfs({source, source}, process_neighbors, {}, visit); + bfs({source, source}, process_neighbors, {}, visit_bool); + bfs({source, source}, process_neighbors, is_target, visit); + bfs({source, source}, process_neighbors, is_target, visit_bool); + + bfs(sources_vec, process_neighbors, is_target, {}); + bfs(sources_vec, process_neighbors, {}, visit); + bfs(sources_vec, process_neighbors, {}, visit_bool); + bfs(sources_vec, process_neighbors, is_target, visit); + bfs(sources_vec, process_neighbors, is_target, visit_bool); + bfs(sources_vec, process_neighbors, is_target, {}); + bfs(sources_vec, process_neighbors, {}, visit); + bfs(sources_vec, process_neighbors, {}, visit_bool); + bfs(sources_vec, process_neighbors, is_target, visit); + bfs(sources_vec, process_neighbors, is_target, visit_bool); + bfs_manual_dedupe(source, process_neighbors, is_target, {}); bfs_manual_dedupe(source, process_neighbors, {}, visit); bfs_manual_dedupe(source, process_neighbors, {}, visit_bool); @@ -847,8 +909,8 @@ void _lint_helper_template( topo_sort(source, process_neighbors); tarjan_scc(source, process_neighbors); - const std::vector sources{source, source}; - tarjan_scc(sources, process_neighbors); + tarjan_scc({source, source}, process_neighbors); + tarjan_scc(sources_vec, process_neighbors); longest_path_dag(source, process_neighbors, get_distance, is_target);