From a9ab6488a392413ed129399e0ee861966cdc97e5 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 3 Aug 2026 18:17:38 +0200 Subject: [PATCH 1/5] wip --- include/gl/algorithm/core.hpp | 13 ++ include/gl/algorithm/templates/bfs.hpp | 72 +++---- include/gl/algorithm/templates/dfs.hpp | 190 +++++++++--------- include/gl/algorithm/templates/pfs.hpp | 84 ++++---- .../traversal/breadth_first_search.hpp | 8 +- .../traversal/depth_first_search.hpp | 22 +- include/gl/algorithm/util.hpp | 43 ++-- 7 files changed, 222 insertions(+), 210 deletions(-) diff --git a/include/gl/algorithm/core.hpp b/include/gl/algorithm/core.hpp index 943cb779..17a23792 100644 --- a/include/gl/algorithm/core.hpp +++ b/include/gl/algorithm/core.hpp @@ -155,6 +155,19 @@ struct search_node { [[nodiscard]] gl_attr_force_inline bool is_root() const noexcept { return this->vertex_id != invalid_id and this->vertex_id == this->pred_id; } + + /// @brief Explicitly converts this node to a search node with a different extension type. + /// + /// This allows for safe, seamless slicing and up-casting between stateful and stateless + /// search nodes during algorithm execution. The new extension is default-initialized. + /// + /// @tparam OtherExt The target extension type. + /// @return A new search node preserving the topology but with the target extension type. + template + requires(not std::same_as) + [[nodiscard]] gl_attr_force_inline explicit operator search_node() const noexcept { + return search_node{this->vertex_id, this->pred_id}; + } }; /// @ingroup GL-Algorithm diff --git a/include/gl/algorithm/templates/bfs.hpp b/include/gl/algorithm/templates/bfs.hpp index dfc18b21..080b9c6a 100644 --- a/include/gl/algorithm/templates/bfs.hpp +++ b/include/gl/algorithm/templates/bfs.hpp @@ -30,9 +30,9 @@ namespace gl::algorithm { /// bool completed = gl::algorithm::bfs( /// graph, /// std::array{gl::algorithm::root_node(start_vertex_id)}, // (2)! -/// gl::algorithm::default_visit_vertex_predicate(visited), // (3)! -/// [&](auto v, auto p) { // (4)! -/// std::cout << "Visited vertex " << v << '\n'; +/// gl::algorithm::default_visit_predicate(visited), // (3)! +/// [&](const auto& node) { // (4)! +/// std::cout << "Visited vertex " << node.vertex_id << '\n'; /// return true; // Continue search /// }, /// gl::algorithm::default_enqueue_node_predicate(visited) // (5)! @@ -41,30 +41,30 @@ namespace gl::algorithm { /// /// 1\. Tracks discovered vertices. /// -/// 2\. Initializes the search queue with the starting vertex. +/// 2\. Initializes the search queue with the starting search node. /// -/// 3\. Predicate ensuring we don't process a vertex if it was already marked visited. +/// 3\. Predicate ensuring we don't process a node if its vertex was already marked visited. /// -/// 4\. The main visit callback. Here we just print the ID. Returning `false` would abort the search. +/// 4\. The main visit callback receiving the full search node. Returning `false` would abort the search. /// -/// 5\. Predicate ensuring we only enqueue adjacent vertices that haven't been visited yet, returning a @ref gl::algorithm::decision "decision". +/// 5\. Predicate evaluating newly constructed target nodes to ensure we only enqueue unvisited vertices, returning a @ref gl::algorithm::decision "decision". /// /// ### Template Parameters /// | Parameter | Description | Constraint | /// | :-------- | :--- | :--- | /// | G | The type of the graph being traversed. | Must satisfy the [**c_graph**](gl_concepts.md#gl-traits-c-graph) concept. | /// | InitNodeRngType | The type of the container providing the initial roots to enqueue. | Must be a *forward range* of @ref gl::algorithm::search_node "search nodes". | -/// | VisitVertexPredicate | Type of the callable deciding if a popped vertex should be processed. | Must be one of:
- An `(id_type) -> bool` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | -/// | VisitCallback | Type of the callable executed when a vertex is officially visited. | Must be one of:
- An `(id_type, id_type) -> bool` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | -/// | EnqueueNodePred | Type of the callable deciding if a node corresponding to an adjacent vertex should be pushed to the queue. | Must be one of:
- An `(id_type, const edge_type&) -> decision` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | -/// | PreVisitCallback | Type of the callable executed immediately before `VisitCallback`. | Must be one of:
- An `(id_type) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | -/// | PostVisitCallback | Type of the callable executed after all adjacent edges are evaluated. | Must be one of:
- An `(id_type) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | +/// | VisitPredicate | Type of the callable deciding if a popped node should be processed. | Must be one of:
- A `(search_node>) -> bool` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | +/// | VisitCallback | Type of the callable executed when a node is officially visited. | Must be one of:
- A `(search_node>) -> bool` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | +/// | EnqueuePredicate | Type of the callable deciding if a target node should be pushed to the queue. | Must be one of:
- A `(search_node>, const edge_t&) -> decision` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | +/// | PreVisitCallback | Type of the callable executed immediately before `VisitCallback`. | Must be one of:
- A `(search_node>) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | +/// | PostVisitCallback | Type of the callable executed after all adjacent edges are evaluated. | Must be one of:
- A `(search_node>) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | /// /// @param graph The graph to traverse. /// @param initial_nodes A range of initial @ref gl::algorithm::search_node "search nodes" to seed the BFS queue. -/// @param visit_vertex_pred Predicate evaluated immediately after popping a vertex. If it returns `false`, the vertex is skipped. -/// @param visit Callback invoked when a vertex is officially visited. If it returns `false`, the entire BFS immediately aborts. -/// @param enqueue_node_pred Predicate evaluated for each outgoing edge. Returns a @ref gl::algorithm::decision "decision": +/// @param visit_pred Predicate evaluated immediately after popping a node. If it returns `false`, the node is skipped. +/// @param visit Callback invoked when a node is officially visited. If it returns `false`, the entire BFS immediately aborts. +/// @param enqueue_pred Predicate evaluated for each outgoing edge and target node. Returns a @ref gl::algorithm::decision "decision": /// - `accept` to enqueue, /// - `reject` to skip, /// - `abort` to terminate the BFS entirely. @@ -76,19 +76,20 @@ template < traits::c_graph G, traits::c_forward_range_of>> InitNodeRngType = std::vector>>, - traits::c_optional_predicate> VisitVertexPredicate = empty_callback, - traits::c_optional_predicate, id_t> VisitCallback = empty_callback, - traits::c_decision_predicate, const edge_t&> EnqueueNodePred = empty_callback, - traits::c_optional_callback> PreVisitCallback = empty_callback, - traits::c_optional_callback> PostVisitCallback = empty_callback> + traits::c_optional_predicate>> VisitPredicate = empty_callback, + traits::c_optional_predicate>> VisitCallback = empty_callback, + traits::c_decision_predicate>, const edge_t&> EnqueuePredicate = + empty_callback, + traits::c_optional_callback>> PreVisitCallback = empty_callback, + traits::c_optional_callback>> PostVisitCallback = empty_callback> bool bfs( G&& graph, const InitNodeRngType& initial_nodes, - VisitVertexPredicate visit_vertex_pred = {}, - VisitCallback visit = {}, - EnqueueNodePred enqueue_node_pred = {}, - PreVisitCallback pre_visit = {}, - PostVisitCallback post_visit = {} + const VisitPredicate& visit_pred = {}, + const VisitCallback& visit = {}, + const EnqueuePredicate& enqueue_pred = {}, + const PreVisitCallback& pre_visit = {}, + const PostVisitCallback& post_visit = {} ) { if (std::ranges::empty(initial_nodes)) return false; @@ -100,31 +101,32 @@ bool bfs( // search the graph while (not q.empty()) { - const auto node = q.front(); + const auto curr_node = q.front(); q.pop(); - if constexpr (not traits::c_empty_callback) - if (not visit_vertex_pred(node.vertex_id)) + if constexpr (not traits::c_empty_callback) + if (not visit_pred(curr_node)) continue; if constexpr (not traits::c_empty_callback) - pre_visit(node.vertex_id); + pre_visit(curr_node); if constexpr (not traits::c_empty_callback) - if (not visit(node.vertex_id, node.pred_id)) + if (not visit(curr_node)) return false; - for (const auto& edge : graph.out_edges(node.vertex_id)) { - const auto target_vertex_id = edge.other(node.vertex_id); - const auto enqueue = enqueue_node_pred(target_vertex_id, edge); + for (const auto& edge : graph.out_edges(curr_node.vertex_id)) { + search_node> tgt_node{edge.other(curr_node.vertex_id), curr_node.vertex_id}; + const auto enqueue = enqueue_pred(tgt_node, edge); + if (enqueue == decision::abort) return false; if (enqueue) - q.emplace(target_vertex_id, node.vertex_id); + q.push(tgt_node); } if constexpr (not traits::c_empty_callback) - post_visit(node.vertex_id); + post_visit(curr_node); } return true; diff --git a/include/gl/algorithm/templates/dfs.hpp b/include/gl/algorithm/templates/dfs.hpp index f1d29bdf..d9ab9855 100644 --- a/include/gl/algorithm/templates/dfs.hpp +++ b/include/gl/algorithm/templates/dfs.hpp @@ -35,9 +35,9 @@ namespace gl::algorithm { /// bool completed = gl::algorithm::dfs( /// graph, /// std::array{gl::algorithm::root_node(start_vertex_id)}, // (2)! -/// gl::algorithm::default_visit_vertex_predicate(visited), // (3)! -/// [&](auto v, auto p) { // (4)! -/// std::cout << "Visited vertex " << v << '\n'; +/// gl::algorithm::default_visit_predicate(visited), // (3)! +/// [&](const auto& node) { // (4)! +/// std::cout << "Visited vertex " << node.vertex_id << '\n'; /// return true; // Continue search /// }, /// gl::algorithm::default_enqueue_node_predicate(visited) // (5)! @@ -46,30 +46,30 @@ namespace gl::algorithm { /// /// 1\. Tracks discovered vertices. /// -/// 2\. Initializes the search stack with the starting vertex. +/// 2\. Initializes the search stack with the starting search node. /// -/// 3\. Predicate ensuring we don't process a vertex if it was already marked visited. +/// 3\. Predicate ensuring we don't process a node if its vertex was already marked visited. /// -/// 4\. The main visit callback. Returning `false` would abort the search. +/// 4\. The main visit callback receiving the full search node. Returning `false` would abort the search. /// -/// 5\. Predicate ensuring we only push adjacent, unvisited vertices to the stack, returning a @ref gl::algorithm::decision "decision". +/// 5\. Predicate evaluating newly constructed target nodes to ensure we only push unvisited vertices to the stack, returning a @ref gl::algorithm::decision "decision". /// /// ### Template Parameters /// | Parameter | Description | Constraint | /// | :-------- | :--- | :--- | /// | G | The type of the graph being traversed. | Must satisfy the [**c_graph**](gl_concepts.md#gl-traits-c-graph) concept. | /// | InitNodeRngType | The type of the container providing the initial roots to push to the stack. | Must be a *forward range* of @ref gl::algorithm::search_node "search nodes". | -/// | VisitVertexPredicate | Type of the callable deciding if a popped vertex should be processed. | Must be one of:
- An `(id_type) -> bool` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | -/// | VisitCallback | Type of the callable executed when a vertex is officially visited. | Must be one of:
- An `(id_type, id_type) -> bool` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | -/// | EnqueueNodePred | Type of the callable deciding if a node corresponding to an adjacent vertex should be pushed to the stack. | Must be one of:
- An `(id_type, const edge_type&) -> decision` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | -/// | PreVisitCallback | Type of the callable executed immediately before `VisitCallback`. | Must be one of:
- An `(id_type) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | -/// | PostVisitCallback | Type of the callable executed after a node's subtree is fully explored. | Must be one of:
- An `(id_type) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | +/// | VisitPredicate | Type of the callable deciding if a popped node should be processed. | Must be one of:
- A `(search_node>) -> bool` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | +/// | VisitCallback | Type of the callable executed when a node is officially visited. | Must be one of:
- A `(search_node>) -> bool` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | +/// | EnqueuePredicate | Type of the callable deciding if a target node should be pushed to the stack. | Must be one of:
- A `(search_node>, const edge_t&) -> decision` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | +/// | PreVisitCallback | Type of the callable executed immediately before `VisitCallback`. | Must be one of:
- A `(search_node>) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | +/// | PostVisitCallback | Type of the callable executed after a node's subtree is fully explored. | Must be one of:
- A `(search_node>) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | /// /// @param graph The graph to traverse. /// @param initial_nodes A range of initial @ref gl::algorithm::search_node "search nodes" to seed the DFS stack. -/// @param visit_vertex_pred Predicate evaluated immediately after popping a vertex. If it returns `false`, the vertex is skipped. -/// @param visit Callback invoked when a vertex is officially visited. If it returns `false`, the entire DFS immediately aborts. -/// @param enqueue_node_pred Predicate evaluated for each outgoing edge. Returns a @ref gl::algorithm::decision "decision": +/// @param visit_pred Predicate evaluated immediately after popping a node. If it returns `false`, the node is skipped. +/// @param visit Callback invoked when a node is officially visited. If it returns `false`, the entire DFS immediately aborts. +/// @param enqueue_pred Predicate evaluated for each outgoing edge and target node. Returns a @ref gl::algorithm::decision "decision": /// - `accept` to enqueue, /// - `reject` to skip, /// - `abort` to terminate the DFS entirely. @@ -81,82 +81,89 @@ template < traits::c_graph G, traits::c_forward_range_of>> InitNodeRngType = std::vector>>, - traits::c_optional_predicate> VisitVertexPredicate = empty_callback, - traits::c_optional_predicate, id_t> VisitCallback = empty_callback, - traits::c_decision_predicate, const edge_t&> EnqueueNodePred = empty_callback, - traits::c_optional_callback> PreVisitCallback = empty_callback, - traits::c_optional_callback> PostVisitCallback = empty_callback> + traits::c_optional_predicate>> VisitPredicate = empty_callback, + traits::c_optional_predicate>> VisitCallback = empty_callback, + traits::c_decision_predicate>, const edge_t&> EnqueuePredicate = + empty_callback, + traits::c_optional_callback>> PreVisitCallback = empty_callback, + traits::c_optional_callback>> PostVisitCallback = empty_callback> bool dfs( G&& graph, const InitNodeRngType& initial_nodes, - VisitVertexPredicate visit_vertex_pred = {}, - VisitCallback visit = {}, - EnqueueNodePred enqueue_node_pred = {}, - PreVisitCallback pre_visit = {}, - PostVisitCallback post_visit = {} + const VisitPredicate& visit_pred = {}, + const VisitCallback& visit = {}, + const EnqueuePredicate& enqueue_pred = {}, + const PreVisitCallback& pre_visit = {}, + const PostVisitCallback& post_visit = {} ) { + using stateless_node_t = search_node>; + if (std::ranges::empty(initial_nodes)) return false; if constexpr (traits::c_empty_callback) { // stateless stack - std::stack>> s; + std::stack s; for (const auto& node : initial_nodes) s.push(node); while (not s.empty()) { - const auto node = s.top(); + const stateless_node_t curr_node = s.top(); s.pop(); - if constexpr (not traits::c_empty_callback) - if (not visit_vertex_pred(node.vertex_id)) + if constexpr (not traits::c_empty_callback) + if (not visit_pred(curr_node)) continue; if constexpr (not traits::c_empty_callback) - pre_visit(node.vertex_id); + pre_visit(curr_node); if constexpr (not traits::c_empty_callback) - if (not visit(node.vertex_id, node.pred_id)) + if (not visit(curr_node)) return false; - for (const auto& edge : graph.out_edges(node.vertex_id)) { - const auto target_vertex_id = edge.other(node.vertex_id); - const auto enqueue = enqueue_node_pred(target_vertex_id, edge); + for (const auto& edge : graph.out_edges(curr_node.vertex_id)) { + stateless_node_t tgt_node{edge.other(curr_node.vertex_id), curr_node.vertex_id}; + const auto enqueue = enqueue_pred(tgt_node, edge); + if (enqueue == decision::abort) return false; if (enqueue) - s.emplace(target_vertex_id, node.vertex_id); + s.push(tgt_node); } } } - else { // statefull stack + else { // stateful stack - struct dfs_extension { - bool expanded = false; // Indicated if all of the node's children have been visited + struct dfs_ext { + bool expanded = false; // Indicates if all of the node's children have been visited }; - using stateful_node_t = search_node, dfs_extension>; + using stateful_node_t = search_node, dfs_ext>; std::stack s; for (const auto& node : initial_nodes) - s.emplace(node.vertex_id, node.pred_id); // Initialize as unexpanded + s.push(stateful_node_t(node)); // Initialize as unexpanded while (not s.empty()) { auto curr_node = s.top(); s.pop(); + // Reconstruct the stateless base node to safely satisfy the callback concepts + const stateless_node_t base_node(curr_node); + if (curr_node.ext.expanded) { - post_visit(curr_node.vertex_id); + post_visit(base_node); } else { - if constexpr (not traits::c_empty_callback) - if (not visit_vertex_pred(curr_node.vertex_id)) + if constexpr (not traits::c_empty_callback) + if (not visit_pred(base_node)) continue; if constexpr (not traits::c_empty_callback) - pre_visit(curr_node.vertex_id); + pre_visit(base_node); if constexpr (not traits::c_empty_callback) - if (not visit(curr_node.vertex_id, curr_node.pred_id)) + if (not visit(base_node)) return false; // Push parent back marked as expanded to wait for children @@ -164,12 +171,13 @@ bool dfs( s.push(curr_node); for (const auto& edge : graph.out_edges(curr_node.vertex_id)) { - const auto target_vertex_id = edge.other(curr_node.vertex_id); - const auto enqueue = enqueue_node_pred(target_vertex_id, edge); + stateless_node_t tgt_base{edge.other(curr_node.vertex_id), curr_node.vertex_id}; + const auto enqueue = enqueue_pred(tgt_base, edge); + if (enqueue == decision::abort) return false; if (enqueue) - s.emplace(target_vertex_id, curr_node.vertex_id); + s.push(stateful_node_t(tgt_base)); } } } @@ -182,7 +190,7 @@ bool dfs( /// @brief A highly customizable, generic recursive Depth-First Search (DFS) algorithm engine. /// /// This engine mirrors the iterative `dfs` behavior but utilizes the C++ call stack. -/// It does not accept an initial range, but instead is kicked off for a specific root vertex. +/// It does not accept an initial range, but instead is kicked off for a specific root node. /// It does not return a boolean abort signal; logic flow must be managed by the injected callbacks. /// /// ### Example Usage @@ -191,92 +199,78 @@ bool dfs( /// /// gl::algorithm::r_dfs( /// graph, -/// start_id, // (2)! -/// gl::algorithm::no_root, // (3)! -/// gl::algorithm::default_visit_vertex_predicate(visited), // (4)! -/// [&](auto v, auto p) { // (5)! -/// std::cout << "Recursively visiting vertex " << v << '\n'; +/// gl::algorithm::root_node(start_id), // (2)! +/// gl::algorithm::default_visit_predicate(visited), // (3)! +/// [&](const auto& node) { // (4)! +/// std::cout << "Recursively visiting vertex " << node.vertex_id << '\n'; /// return true; /// }, -/// gl::algorithm::default_enqueue_node_predicate(visited) // (6)! +/// gl::algorithm::default_enqueue_node_predicate(visited) // (5)! /// ); /// ``` /// /// 1\. Tracks discovered vertices. /// -/// 2\. The ID of the starting vertex for the recursion. -/// -/// 3\. Indicates that the starting vertex has no predecessor. +/// 2\. A fully initialized root search node to begin the recursion. /// -/// 4\. Predicate evaluated upon entering the recursive call to prevent duplicate processing. +/// 3\. Predicate evaluated upon entering the recursive call to prevent duplicate processing. /// -/// 5\. The main visit callback. +/// 4\. The main visit callback. /// -/// 6\. Predicate evaluating whether to recursively traverse into the target vertex. +/// 5\. Predicate evaluating whether to recursively traverse into the target node. /// /// ### Template Parameters /// | Parameter | Description | Constraint | /// | :-------- | :--- | :--- | /// | G | The type of the graph being traversed. | Must satisfy the [**c_graph**](gl_concepts.md#gl-traits-c-graph) concept. | -/// | VisitVertexPredicate | Type of the callable deciding if the current vertex should be processed. | Must be one of:
- An `(id_type) -> bool` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | -/// | VisitCallback | Type of the callable executed when the vertex is officially visited. | Must be one of:
- An `(id_type, id_type) -> bool` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | -/// | EnqueueNodePred | Type of the callable deciding if a node corresponding to an adjacent vertex should be recursed into. | Must be one of:
- An `(id_type, const edge_type&) -> decision` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | -/// | PreVisitCallback | Type of the callable executed immediately before `VisitCallback`. | Must be one of:
- An `(id_type) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | -/// | PostVisitCallback | Type of the callable executed after all adjacent edges are evaluated. | Must be one of:
- An `(id_type) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | +/// | VisitPredicate | Type of the callable deciding if the current node should be processed. | Must be one of:
- A `(search_node>) -> bool` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | +/// | VisitCallback | Type of the callable executed when the node is officially visited. | Must be one of:
- A `(search_node>) -> bool` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | +/// | EnqueuePredicate | Type of the callable deciding if a target node should be recursed into. | Must be one of:
- A `(search_node>, const edge_t&) -> decision` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | +/// | PreVisitCallback | Type of the callable executed immediately before `VisitCallback`. | Must be one of:
- A `(search_node>) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | +/// | PostVisitCallback | Type of the callable executed after all adjacent edges are evaluated. | Must be one of:
- A `(search_node>) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | /// /// @param graph The graph to traverse. -/// @param vertex_id The ID of the vertex currently being visited. -/// @param pred_id The ID of the predecessor vertex. -/// @param visit_vertex_pred Predicate evaluated immediately upon entry. If it returns `false`, recursion returns early. -/// @param visit Callback invoked when a vertex is officially visited. -/// @param enqueue_node_pred Predicate evaluated for each outgoing edge. If `true`, the target is recursed into. +/// @param curr_node The active @ref gl::algorithm::search_node "search node" currently being evaluated. +/// @param visit_pred Predicate evaluated immediately upon entry. If it returns `false`, recursion returns early. +/// @param visit Callback invoked when a node is officially visited. +/// @param enqueue_pred Predicate evaluated for each outgoing edge and target node. If `true`, the target is recursed into. /// @param pre_visit Hook executed immediately before the `visit` callback. /// @param post_visit Hook executed after returning from all adjacent recursive calls. /// @hideparams template < traits::c_graph G, - traits::c_optional_predicate> VisitVertexPredicate, - traits::c_optional_predicate, id_t> VisitCallback, - traits::c_decision_predicate, const edge_t&> EnqueueNodePred, - traits::c_optional_callback> PreVisitCallback = empty_callback, - traits::c_optional_callback> PostVisitCallback = empty_callback> + traits::c_optional_predicate>> VisitPredicate, + traits::c_optional_predicate>> VisitCallback, + traits::c_decision_predicate>, const edge_t&> EnqueuePredicate, + traits::c_optional_callback>> PreVisitCallback = empty_callback, + traits::c_optional_callback>> PostVisitCallback = empty_callback> void r_dfs( G&& graph, - const id_t vertex_id, - const id_t pred_id, - VisitVertexPredicate visit_vertex_pred, + search_node> curr_node, + VisitPredicate visit_pred, VisitCallback visit, - EnqueueNodePred enqueue_node_pred, + EnqueuePredicate enqueue_pred, PreVisitCallback pre_visit = {}, PostVisitCallback post_visit = {} ) { - if constexpr (not traits::c_empty_callback) - if (not visit_vertex_pred(vertex_id)) + if constexpr (not traits::c_empty_callback) + if (not visit_pred(curr_node)) return; if constexpr (not traits::c_empty_callback) - pre_visit(vertex_id); + pre_visit(curr_node); - visit(vertex_id, pred_id); + visit(curr_node); // recursively search vertices adjacent to the current vertex - for (const auto& edge : graph.out_edges(vertex_id)) { - const auto target_vertex_id = edge.other(vertex_id); - if (enqueue_node_pred(target_vertex_id, edge)) - r_dfs( - graph, - target_vertex_id, - vertex_id, - visit_vertex_pred, - visit, - enqueue_node_pred, - pre_visit, - post_visit - ); + for (const auto& edge : graph.out_edges(curr_node.vertex_id)) { + search_node> tgt_node{edge.other(curr_node.vertex_id), curr_node.vertex_id}; + if (enqueue_pred(tgt_node, edge)) + r_dfs(graph, tgt_node, visit_pred, visit, enqueue_pred, pre_visit, post_visit); } if constexpr (not traits::c_empty_callback) - post_visit(vertex_id); + post_visit(curr_node); } } // namespace gl::algorithm diff --git a/include/gl/algorithm/templates/pfs.hpp b/include/gl/algorithm/templates/pfs.hpp index 88466875..2e4dcd23 100644 --- a/include/gl/algorithm/templates/pfs.hpp +++ b/include/gl/algorithm/templates/pfs.hpp @@ -2,7 +2,7 @@ // This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). // Licensed under the MIT License. See the LICENSE file in the project root for full license information. -/// @file gl/algorithm/pfs.hpp +/// @file gl/algorithm/templates/pfs.hpp /// @brief Generic Priority-First Search (PFS) template algorithm engine. #pragma once @@ -34,10 +34,10 @@ namespace gl::algorithm { /// [](const auto& lhs, const auto& rhs) { // (2)! /// return lhs.vertex_id > rhs.vertex_id; /// }, -/// std::array{gl::algorithm::root_node(start_vertex_id)}, // (3)! -/// gl::algorithm::default_visit_vertex_predicate(visited), // (4)! -/// [&](auto v, auto p) { // (5)! -/// std::cout << "Priority visited vertex " << v << '\n'; +/// std::array{gl::algorithm::root_node(start_vertex_id)}, // (3)! +/// gl::algorithm::default_visit_predicate(visited), // (4)! +/// [&](const auto& node) { // (5)! +/// std::cout << "Priority visited vertex " << node.vertex_id << '\n'; /// return true; // Continue search /// }, /// gl::algorithm::default_enqueue_node_predicate(visited) // (6)! @@ -48,13 +48,13 @@ namespace gl::algorithm { /// /// 2\. Injects the comparator to order the search exploration. A min-heap based on vertex IDs. /// -/// 3\. Initializes the priority queue with the starting vertex. By default, this yields standard @ref gl::algorithm::search_node "search_nodes". +/// 3\. Initializes the priority queue with the starting node. By default, this yields standard @ref gl::algorithm::search_node "search_nodes". /// /// 4\. Predicate evaluated after popping the highest priority node. Accepts the entire `NodeType` to allow for snapshot inspections (like stale-node rejection). /// -/// 5\. The main visit callback. Returning `false` aborts the search. +/// 5\. The main visit callback receiving the full search node. Returning `false` aborts the search. /// -/// 6\. Predicate determining if an adjacent vertex should be pushed into the priority queue. +/// 6\. Predicate determining if an adjacent target node should be pushed into the priority queue. /// /// ### Template Parameters /// | Parameter | Description | Constraint | @@ -63,23 +63,23 @@ namespace gl::algorithm { /// | PQCmp | The comparator used to order elements within the priority queue. | Must be a `(NodeType, NodeType) -> bool` callable. | /// | InitNodeRngType | The container providing the initial roots to enqueue. | Must satisfy `std::ranges::forward_range` and yield a @ref gl::algorithm::search_node "search_node". | /// | NodeType | The exact search node type extracted implicitly from the range. | Must strictly match `search_node, Extension>`. | -/// | VisitVertexPredicate | Decides if a popped node should be processed. | Must be one of:
- `(NodeType) -> bool` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | -/// | VisitCallback | Executed when a vertex is officially visited. | Must be one of:
- `(id_type, id_type) -> bool` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | -/// | EnqueueNodePred | Decides if a node corresponding to an adjacent vertex should be pushed to the queue. | Must be one of:
- `(id_type, const edge_type&) -> decision` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | -/// | MakeNodeCallback | Constructs a custom `NodeType` before pushing to the queue. | Must be one of:
- `(id_type, id_type, const edge_type&) -> NodeType` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | -/// | PreVisitCallback | Executed immediately before `VisitCallback`. | Must be one of:
- `(id_type) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | -/// | PostVisitCallback | Executed after all adjacent edges are evaluated. | Must be one of:
- `(id_type) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | +/// | VisitPredicate | Decides if a popped node should be processed. | Must be one of:
- A `(const NodeType&) -> bool` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | +/// | VisitCallback | Executed when a node is officially visited. | Must be one of:
- A `(const NodeType&) -> bool` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | +/// | EnqueuePredicate | Decides if a target node should be pushed to the queue. | Must be one of:
- A `(const search_node>&, const edge_t&) -> decision` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | +/// | MakeNodeCallback | Constructs a custom `NodeType` before pushing to the queue. | Must be one of:
- A `(id_type, id_type, const edge_t&) -> NodeType` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | +/// | PreVisitCallback | Executed immediately before `VisitCallback`. | Must be one of:
- A `(const NodeType&) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | +/// | PostVisitCallback | Executed after all adjacent edges are evaluated. | Must be one of:
- A `(const NodeType&) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | /// /// @param graph The graph to traverse. /// @param pq_cmp The comparator instance used to determine priority (highest priority is popped first). -/// @param initial_nodes A range of initial @ref gl::algorithm::search_node "search nodes" to seed the priority queue. -/// @param visit_vertex_pred Predicate evaluated immediately after popping a node. If it returns `false`, the node is skipped (often used for late-rejection in Dijkstra). -/// @param visit Callback invoked when a vertex is officially visited. If it returns `false`, the entire PFS immediately aborts. -/// @param enqueue_node_pred Predicate evaluated for each outgoing edge. Returns a @ref gl::algorithm::decision "decision": +/// @param initial_nodes A range of initial nodes to seed the priority queue. +/// @param visit_pred Predicate evaluated immediately after popping a node. If it returns `false`, the node is skipped (often used for late-rejection in Dijkstra). +/// @param visit Callback invoked when a node is officially visited. If it returns `false`, the entire PFS immediately aborts. +/// @param enqueue_pred Predicate evaluated for each outgoing edge and target node. Returns a @ref gl::algorithm::decision "decision": /// - `accept` to enqueue, /// - `reject` to skip, /// - `abort` to terminate the PFS entirely. -/// @param make_node Factory callback to construct a custom node prior to enqueueing (useful for computing extensions like cumulative weights). Defaults to injecting a default-extended `search_node`. +/// @param make_node Factory callback to construct a custom stateful node prior to enqueueing (useful for computing extensions like cumulative weights). Defaults to injecting a default-extended `NodeType`. /// @param pre_visit Hook executed immediately before the `visit` callback. /// @param post_visit Hook executed after all adjacent edges of the current vertex have been evaluated. /// @return `true` if the queue was exhausted naturally, `false` if the search was aborted early by a callback or predicate. @@ -89,24 +89,25 @@ template < typename PQCmp, typename InitNodeRngType = std::vector>>, typename NodeType = std::ranges::range_value_t, - traits::c_optional_predicate VisitVertexPredicate = empty_callback, - traits::c_optional_predicate, id_t> VisitCallback = empty_callback, - traits::c_decision_predicate, const edge_t&> EnqueueNodePred = empty_callback, + traits::c_optional_predicate VisitPredicate = empty_callback, + traits::c_optional_predicate VisitCallback = empty_callback, + traits::c_decision_predicate>, const edge_t&> EnqueuePredicate = + empty_callback, traits::c_optional_callback, id_t, const edge_t&> MakeNodeCallback = empty_callback, - traits::c_optional_callback> PreVisitCallback = empty_callback, - traits::c_optional_callback> PostVisitCallback = empty_callback> + traits::c_optional_callback PreVisitCallback = empty_callback, + traits::c_optional_callback PostVisitCallback = empty_callback> requires(traits::c_predicate and traits::c_instantiation_of and std::same_as>) bool pfs( G&& graph, const PQCmp& pq_cmp, const InitNodeRngType& initial_nodes, - VisitVertexPredicate visit_vertex_pred = {}, - VisitCallback visit = {}, - EnqueueNodePred enqueue_node_pred = {}, - MakeNodeCallback make_node = {}, - PreVisitCallback pre_visit = {}, - PostVisitCallback post_visit = {} + const VisitPredicate& visit_pred = {}, + const VisitCallback& visit = {}, + const EnqueuePredicate& enqueue_pred = {}, + const MakeNodeCallback& make_node = {}, + const PreVisitCallback& pre_visit = {}, + const PostVisitCallback& post_visit = {} ) { if (std::ranges::empty(initial_nodes)) return false; @@ -120,37 +121,38 @@ bool pfs( // search the graph while (not q.empty()) { - const auto node = q.top(); + const auto curr_node = q.top(); q.pop(); - if constexpr (not traits::c_empty_callback) - if (not visit_vertex_pred(node)) + if constexpr (not traits::c_empty_callback) + if (not visit_pred(curr_node)) continue; if constexpr (not traits::c_empty_callback) - pre_visit(node.vertex_id); + pre_visit(curr_node); if constexpr (not traits::c_empty_callback) - if (not visit(node.vertex_id, node.pred_id)) + if (not visit(curr_node)) return false; - for (const auto& edge : graph.out_edges(node.vertex_id)) { - const auto target_vertex_id = edge.other(node.vertex_id); - const auto enqueue = enqueue_node_pred(target_vertex_id, edge); + for (const auto& edge : graph.out_edges(curr_node.vertex_id)) { + const auto target_vertex_id = edge.other(curr_node.vertex_id); + search_node> tgt_base_node{target_vertex_id, curr_node.vertex_id}; + const auto enqueue = enqueue_pred(tgt_base_node, edge); if (enqueue == decision::abort) return false; if (enqueue) { if constexpr (not traits::c_empty_callback) - q.push(make_node(target_vertex_id, node.vertex_id, edge)); + q.push(make_node(target_vertex_id, curr_node.vertex_id, edge)); else - q.push(NodeType{target_vertex_id, node.vertex_id}); + q.push(NodeType{target_vertex_id, curr_node.vertex_id}); } } if constexpr (not traits::c_empty_callback) - post_visit(node.vertex_id); + post_visit(curr_node); } return true; diff --git a/include/gl/algorithm/traversal/breadth_first_search.hpp b/include/gl/algorithm/traversal/breadth_first_search.hpp index 87d53b5c..0a641857 100644 --- a/include/gl/algorithm/traversal/breadth_first_search.hpp +++ b/include/gl/algorithm/traversal/breadth_first_search.hpp @@ -89,9 +89,9 @@ result_type> breadth_first_search( bfs( graph, std::array{gl::algorithm::root_node(root_vertex_id)}, - default_visit_vertex_predicate(visited), + default_visit_predicate(visited), default_visit_callback(visited, pred_map), - default_enqueue_node_predicate(visited), + default_enqueue_predicate(visited), pre_visit, post_visit ); @@ -101,9 +101,9 @@ result_type> breadth_first_search( bfs( graph, std::array{gl::algorithm::root_node(root_id)}, - default_visit_vertex_predicate(visited), + default_visit_predicate(visited), default_visit_callback(visited, pred_map), - default_enqueue_node_predicate(visited), + default_enqueue_predicate(visited), pre_visit, post_visit ); diff --git a/include/gl/algorithm/traversal/depth_first_search.hpp b/include/gl/algorithm/traversal/depth_first_search.hpp index 09214df7..f25b5c35 100644 --- a/include/gl/algorithm/traversal/depth_first_search.hpp +++ b/include/gl/algorithm/traversal/depth_first_search.hpp @@ -97,9 +97,9 @@ result_type> depth_first_search( dfs( graph, std::array{gl::algorithm::root_node(root_vertex_id)}, - default_visit_vertex_predicate(visited), + default_visit_predicate(visited), default_visit_callback(visited, pred_map), - default_enqueue_node_predicate(visited), + default_enqueue_predicate(visited), pre_visit, post_visit ); @@ -109,9 +109,9 @@ result_type> depth_first_search( dfs( graph, std::array{gl::algorithm::root_node(root_id)}, - default_visit_vertex_predicate(visited), + default_visit_predicate(visited), default_visit_callback(visited, pred_map), - default_enqueue_node_predicate(visited), + default_enqueue_predicate(visited), pre_visit, post_visit ); @@ -194,11 +194,10 @@ result_type> recursive_depth_first_search( if (root_vertex_id != no_root) { r_dfs( graph, - root_vertex_id, - root_vertex_id, // pred_id - default_visit_vertex_predicate(visited), + root_node(root_vertex_id), + default_visit_predicate(visited), default_visit_callback(visited, pred_map), - default_enqueue_node_predicate(visited), + default_enqueue_predicate(visited), pre_visit, post_visit ); @@ -207,11 +206,10 @@ result_type> recursive_depth_first_search( for (const auto& root_id : graph.vertex_ids()) r_dfs( graph, - root_id, - root_id, // pred_id - default_visit_vertex_predicate(visited), + root_node(root_id), + default_visit_predicate(visited), default_visit_callback(visited, pred_map), - default_enqueue_node_predicate(visited), + default_enqueue_predicate(visited), pre_visit, post_visit ); diff --git a/include/gl/algorithm/util.hpp b/include/gl/algorithm/util.hpp index 758c7291..4747e125 100644 --- a/include/gl/algorithm/util.hpp +++ b/include/gl/algorithm/util.hpp @@ -45,46 +45,49 @@ template } /// @ingroup GL-Algorithm -/// @brief Generates a default lambda predicate that checks if a vertex has not yet been visited. -/// @param visited A reference to the boolean array tracking visited vertices. -/// @return A callable predicate evaluating to `true` if the vertex is unvisited. -[[nodiscard]] gl_attr_force_inline auto default_visit_vertex_predicate(std::vector& visited) { - return [&](traits::c_id_type auto vertex_id) -> bool { return not visited[to_idx(vertex_id)]; }; +/// @brief Generates a default lambda predicate that checks if a popped search node has already been visited. +/// @tparam G The type of the graph. +/// @param visited_v A reference to the boolean array tracking visited vertices. +/// @return A callable predicate evaluating to `true` if the vertex in the node is unvisited. +template +[[nodiscard]] gl_attr_force_inline auto default_visit_predicate(std::vector& visited_v) { + return [&visited_v](const search_node> node) -> bool { + return not visited_v[to_idx(node.vertex_id)]; + }; } /// @ingroup GL-Algorithm -/// @brief Generates a default lambda callback that marks a vertex as visited and updates the predecessor map. +/// @brief Generates a default lambda callback that marks a node's vertex as visited and updates the predecessor map. /// @tparam G The type of the graph. /// @tparam Result The static discriminator indicating if the predecessor map should be updated. -/// @param visited A reference to the boolean array tracking visited vertices. +/// @param visited_v A reference to the boolean array tracking visited vertices. /// @param pred_map A reference to the active predecessor map. -/// @return A callable callback that executes state updates upon visiting a vertex. +/// @return A callable callback that executes state updates upon officially visiting a node. /// @hideparams template [[nodiscard]] gl_attr_force_inline auto default_visit_callback( - std::vector& visited, non_void_result_type>& pred_map + std::vector& visited_v, non_void_result_type>& pred_map ) { - using id_type = id_t; - return [&](id_type vertex_id, id_type pred_id) { - const auto vertex_idx = to_idx(vertex_id); - visited[vertex_idx] = true; + return [&visited_v, &pred_map](const search_node> node) { + const auto vertex_idx = to_idx(node.vertex_id); + visited_v[vertex_idx] = true; if constexpr (Result == ret) - pred_map[vertex_idx] = pred_id; + pred_map[vertex_idx] = node.pred_id; return true; }; } /// @ingroup GL-Algorithm -/// @brief Generates a default lambda predicate that checks if a node corresponding to an adjacent vertex should be enqueued into the search container. +/// @brief Generates a default lambda predicate that checks if a newly constructed target node should be enqueued. /// @tparam G The type of the graph. /// @tparam AsDecision If `true`, the generated predicate returns a @ref gl::algorithm::decision "decision" instead of a raw boolean. -/// @param visited A reference to the boolean array tracking visited vertices. -/// @return A callable predicate that returns `true` (or `decision::accept`) if the adjacent vertex has not been visited. +/// @param visited_v A reference to the boolean array tracking visited vertices. +/// @return A callable predicate that returns `true` (or `decision::accept`) if the target node's vertex has not been visited. template -[[nodiscard]] gl_attr_force_inline auto default_enqueue_node_predicate(std::vector& visited) { +[[nodiscard]] gl_attr_force_inline auto default_enqueue_predicate(std::vector& visited_v) { using return_t = std::conditional_t; - return [&](id_t vertex_id, const edge_t&) -> return_t { - return not visited[to_idx(vertex_id)]; + return [&visited_v](const search_node> tgt_node, const edge_t&) -> return_t { + return return_t(not visited_v[to_idx(tgt_node.vertex_id)]); }; } From d0019b7419ef75f5673ab4c19659e4d80db816b8 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 3 Aug 2026 18:45:19 +0200 Subject: [PATCH 2/5] works? --- include/gl/algorithm/pathfinding/dijkstra.hpp | 19 +++++----- include/gl/algorithm/topology/coloring.hpp | 18 +++++----- .../algorithm/topology/topological_sort.hpp | 16 ++++----- .../traversal/breadth_first_search.hpp | 8 ++--- .../traversal/depth_first_search.hpp | 12 +++---- tests/source/gl/test_alg_bfs.cpp | 18 +++++----- tests/source/gl/test_alg_dfs.cpp | 36 +++++++++---------- 7 files changed, 62 insertions(+), 65 deletions(-) diff --git a/include/gl/algorithm/pathfinding/dijkstra.hpp b/include/gl/algorithm/pathfinding/dijkstra.hpp index a980145e..c3292c20 100644 --- a/include/gl/algorithm/pathfinding/dijkstra.hpp +++ b/include/gl/algorithm/pathfinding/dijkstra.hpp @@ -95,8 +95,8 @@ template /// | Parameter | Description | Constraint | /// | :-------- | :--- | :--- | /// | G | The type of the graph being traversed. Must define a valid distance/weight property. | Must satisfy the [**c_graph**](gl_concepts.md#gl-traits-c-graph) concept. | -/// | PreVisitCallback | Type of the callable executed immediately before a vertex is officially visited. | Must be one of:
- `(id_type) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | -/// | PostVisitCallback | Type of the callable executed after all adjacent edges of a vertex are evaluated. | Must be one of:
- `(id_type) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | +/// | PreVisitCallback | Type of the callable executed immediately before `VisitCallback`. | Must be one of:
- A `(search_node>) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | +/// | PostVisitCallback | Type of the callable executed after all adjacent edges are evaluated. | Must be one of:
- A `(search_node>) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | /// /// @param graph The graph to evaluate. /// @param source_id The starting vertex ID for the shortest path calculation. @@ -107,8 +107,8 @@ template /// @hideparams template < traits::c_graph G, - traits::c_optional_callback> PreVisitCallback = empty_callback, - traits::c_optional_callback> PostVisitCallback = empty_callback> + traits::c_optional_callback>> PreVisitCallback = empty_callback, + traits::c_optional_callback>> PostVisitCallback = empty_callback> [[nodiscard]] paths_descriptor_type dijkstra_shortest_paths( G&& graph, id_t source_id, PreVisitCallback pre_visit = {}, PostVisitCallback post_visit = {} ) { @@ -142,9 +142,8 @@ template < return node.ext.distance <= paths.distances[to_idx(node.vertex_id)]; }, empty_callback{}, // visit callback - [&paths, &negative_edge](id_type vertex_id, const edge_type& in_edge) + [&paths, &negative_edge](search_node> node, const edge_type& in_edge) -> decision { // enqueue predicate - const auto pred_id = in_edge.other(vertex_id); const auto edge_weight = get_weight(in_edge); if (edge_weight < 0) { @@ -152,13 +151,13 @@ template < return decision::abort; } - const auto new_distance = paths.distances[to_idx(pred_id)] + edge_weight; - auto& v_pred = paths.predecessors[to_idx(vertex_id)]; - auto& v_dist = paths.distances[to_idx(vertex_id)]; + const auto new_distance = paths.distances[node.pred_id] + edge_weight; + auto& v_pred = paths.predecessors[node.vertex_id]; + auto& v_dist = paths.distances[node.vertex_id]; if (v_pred == invalid_id or new_distance < v_dist) { v_dist = new_distance; - v_pred = pred_id; + v_pred = node.pred_id; return true; } diff --git a/include/gl/algorithm/topology/coloring.hpp b/include/gl/algorithm/topology/coloring.hpp index 5f68b445..b4886675 100644 --- a/include/gl/algorithm/topology/coloring.hpp +++ b/include/gl/algorithm/topology/coloring.hpp @@ -49,8 +49,8 @@ using bicoloring_type = std::vector; /// | Parameter | Description | Constraint | /// | :-------- | :--- | :--- | /// | G | The type of the graph being traversed. | Must satisfy the [**c_graph**](gl_concepts.md#gl-traits-c-graph) concept. | -/// | PreVisitCallback | Type of the callable executed immediately before a vertex is officially visited. | Must be one of:
- `(id_type) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | -/// | PostVisitCallback | Type of the callable executed after all adjacent edges of a vertex are evaluated. | Must be one of:
- `(id_type) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | +/// | PreVisitCallback | Type of the callable executed immediately before `VisitCallback`. | Must be one of:
- A `(search_node>) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | +/// | PostVisitCallback | Type of the callable executed after all adjacent edges are evaluated. | Must be one of:
- A `(search_node>) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | /// /// @param graph The graph to evaluate. /// @param pre_visit Hook executed immediately before the internal visit logic. @@ -62,8 +62,8 @@ using bicoloring_type = std::vector; /// @hideparams template < traits::c_graph G, - traits::c_optional_callback> PreVisitCallback = empty_callback, - traits::c_optional_callback> PostVisitCallback = empty_callback> + traits::c_optional_callback>> PreVisitCallback = empty_callback, + traits::c_optional_callback>> PostVisitCallback = empty_callback> [[nodiscard]] std::optional bipartite_coloring( G&& graph, PreVisitCallback pre_visit = {}, PostVisitCallback post_visit = {} ) { @@ -80,15 +80,13 @@ template < std::array{gl::algorithm::root_node(root_id)}, empty_callback{}, // visit predicate empty_callback{}, // visit callback - [&coloring](id_t vertex_id, const edge_t& in_edge) + [&coloring](search_node> node, const edge_t&) -> decision { // enqueue predicate - if (in_edge.is_loop()) + if (node.vertex_id == node.pred_id) return decision::abort; // graph is not bipartite - const auto pred_id = in_edge.other(vertex_id); - - auto& v_color = coloring[to_idx(vertex_id)]; - auto p_color = coloring[to_idx(pred_id)]; + auto& v_color = coloring[to_idx(node.vertex_id)]; + auto p_color = coloring[to_idx(node.pred_id)]; if (v_color == p_color) return decision::abort; // graph is not bipartite diff --git a/include/gl/algorithm/topology/topological_sort.hpp b/include/gl/algorithm/topology/topological_sort.hpp index c5afbd18..73ee629d 100644 --- a/include/gl/algorithm/topology/topological_sort.hpp +++ b/include/gl/algorithm/topology/topological_sort.hpp @@ -51,8 +51,8 @@ namespace gl::algorithm { /// | Parameter | Description | Constraint | /// | :-------- | :--- | :--- | /// | G | The type of the directed graph being traversed. | Must satisfy the [**c_directed_graph**](gl_concepts.md#gl-traits-c-directed-graph) concept. | -/// | PreVisitCallback | Type of the callable executed immediately before a vertex is pushed into the sort order. | Must be one of:
- `(id_type) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | -/// | PostVisitCallback | Type of the callable executed after all adjacent edges of a vertex are evaluated. | Must be one of:
- `(id_type) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | +/// | PreVisitCallback | Type of the callable executed immediately before `VisitCallback`. | Must be one of:
- A `(search_node>) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | +/// | PostVisitCallback | Type of the callable executed after all adjacent edges are evaluated. | Must be one of:
- A `(search_node>) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | /// /// @param graph The directed graph to evaluate. /// @param pre_visit Hook executed immediately before the internal sort logic processes a vertex. @@ -61,8 +61,8 @@ namespace gl::algorithm { /// @hideparams template < traits::c_directed_graph G, - traits::c_optional_callback> PreVisitCallback = empty_callback, - traits::c_optional_callback> PostVisitCallback = empty_callback> + traits::c_optional_callback>> PreVisitCallback = empty_callback, + traits::c_optional_callback>> PostVisitCallback = empty_callback> [[nodiscard]] std::optional>> topological_sort( G&& graph, PreVisitCallback pre_visit = {}, PostVisitCallback post_visit = {} ) { @@ -86,15 +86,15 @@ template < graph, source_vertex_list, empty_callback{}, // visit predicate - [&topological_order](id_type vertex_id, id_type) { // visit callback - topological_order.push_back(vertex_id); + [&topological_order](search_node> node) { // visit callback + topological_order.push_back(node.vertex_id); return true; }, - [&in_degree_map](id_type vertex_id, const edge_type& in_edge) + [&in_degree_map](search_node> node, const edge_type& in_edge) -> decision { // enqueue predicate if (in_edge.is_loop()) return false; - return --in_degree_map[to_idx(vertex_id)] == 0uz; + return --in_degree_map[node.vertex_id] == 0uz; }, pre_visit, post_visit diff --git a/include/gl/algorithm/traversal/breadth_first_search.hpp b/include/gl/algorithm/traversal/breadth_first_search.hpp index 0a641857..476b2738 100644 --- a/include/gl/algorithm/traversal/breadth_first_search.hpp +++ b/include/gl/algorithm/traversal/breadth_first_search.hpp @@ -58,8 +58,8 @@ namespace gl::algorithm { /// | :-------- | :--- | :--- | /// | Result | Discriminator dictating if the algorithm should return a predecessor map (`ret`) or `void` (`noret`). | Must be a valid @ref gl::algorithm::result_discriminator "result_discriminator" enum value. | /// | G | The type of the graph being traversed. | Must satisfy the [**c_graph**](gl_concepts.md#gl-traits-c-graph) concept. | -/// | PreVisitCallback | Type of the callable executed immediately before a vertex is officially visited. | Must be one of:
- `(id_type) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | -/// | PostVisitCallback | Type of the callable executed after all adjacent edges of a vertex are evaluated. | Must be one of:
- `(id_type) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | +/// | PreVisitCallback | Type of the callable executed immediately before `VisitCallback`. | Must be one of:
- A `(search_node>) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | +/// | PostVisitCallback | Type of the callable executed after all adjacent edges are evaluated. | Must be one of:
- A `(search_node>) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | /// /// @param graph The graph to traverse. /// @param root_vertex_id The starting vertex for the search. Defaults to @ref gl::algorithm::no_root "no_root" to traverse the entire graph. @@ -70,8 +70,8 @@ namespace gl::algorithm { template < result_discriminator Result = ret, traits::c_graph G, - traits::c_optional_callback> PreVisitCallback = empty_callback, - traits::c_optional_callback> PostVisitCallback = empty_callback> + traits::c_optional_callback>> PreVisitCallback = empty_callback, + traits::c_optional_callback>> PostVisitCallback = empty_callback> result_type> breadth_first_search( G&& graph, const id_t root_vertex_id = no_root, diff --git a/include/gl/algorithm/traversal/depth_first_search.hpp b/include/gl/algorithm/traversal/depth_first_search.hpp index f25b5c35..556b1434 100644 --- a/include/gl/algorithm/traversal/depth_first_search.hpp +++ b/include/gl/algorithm/traversal/depth_first_search.hpp @@ -78,8 +78,8 @@ namespace gl::algorithm { template < result_discriminator Result = ret, traits::c_graph G, - traits::c_optional_callback> PreVisitCallback = empty_callback, - traits::c_optional_callback> PostVisitCallback = empty_callback> + traits::c_optional_callback>> PreVisitCallback = empty_callback, + traits::c_optional_callback>> PostVisitCallback = empty_callback> result_type> depth_first_search( G&& graph, const id_t root_vertex_id = no_root, @@ -166,8 +166,8 @@ result_type> depth_first_search( /// | :-------- | :--- | :--- | /// | Result | @ref gl::algorithm::result_discriminator "Discriminator" dictating if the algorithm should return a predecessor map (`ret`) or `void` (`noret`). | Must be a valid @ref gl::algorithm::result_discriminator "result_discriminator" enum value. | /// | G | The type of the graph being traversed. | Must satisfy the [**c_graph**](gl_concepts.md#gl-traits-c-graph) concept. | -/// | PreVisitCallback | Type of the callable executed immediately before a vertex is officially visited. | Must be one of:
- `(id_type) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | -/// | PostVisitCallback | Type of the callable executed after all adjacent edges of a vertex are evaluated. | Must be one of:
- `(id_type) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | +/// | PreVisitCallback | Type of the callable executed immediately before `VisitCallback`. | Must be one of:
- A `(search_node>) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | +/// | PostVisitCallback | Type of the callable executed after all adjacent edges are evaluated. | Must be one of:
- A `(search_node>) -> void` callable
- An @ref gl::algorithm::empty_callback "empty_callback" | /// /// @param graph The graph to traverse. /// @param root_vertex_id The starting vertex for the search. Defaults to @ref gl::algorithm::no_root "no_root" to traverse the entire graph. @@ -178,8 +178,8 @@ result_type> depth_first_search( template < result_discriminator Result = ret, traits::c_graph G, - traits::c_optional_callback> PreVisitCallback = empty_callback, - traits::c_optional_callback> PostVisitCallback = empty_callback> + traits::c_optional_callback>> PreVisitCallback = empty_callback, + traits::c_optional_callback>> PostVisitCallback = empty_callback> result_type> recursive_depth_first_search( G&& graph, const id_t root_vertex_id = no_root, diff --git a/tests/source/gl/test_alg_bfs.cpp b/tests/source/gl/test_alg_bfs.cpp index e1913919..7f140661 100644 --- a/tests/source/gl/test_alg_bfs.cpp +++ b/tests/source/gl/test_alg_bfs.cpp @@ -63,12 +63,12 @@ TEST_CASE_TEMPLATE_DEFINE( gl::algorithm::breadth_first_search( graph, gl::algorithm::no_root, - [&](const gl::default_id_type vertex_id) { // previsit - previsit_order.push_back(vertex_id); + [&](const auto node) { // previsit + previsit_order.push_back(node.vertex_id); }, - [&](const gl::default_id_type vertex_id) { // postvisit - postvisit_order.push_back(vertex_id); - vertex_properties[vertex_id].visited = true; + [&](const auto node) { // postvisit + postvisit_order.push_back(node.vertex_id); + vertex_properties[node.vertex_id].visited = true; } ); @@ -144,11 +144,11 @@ TEST_CASE_TEMPLATE_DEFINE( gl::algorithm::breadth_first_search( graph, root_vertex_id, - [&](const gl::default_id_type vertex_id) { // previsit - previsit_order.push_back(vertex_id); + [&](const auto node) { // previsit + previsit_order.push_back(node.vertex_id); }, - [&](const gl::default_id_type vertex_id) { // postvisit - postvisit_order.push_back(vertex_id); + [&](const auto node) { // postvisit + postvisit_order.push_back(node.vertex_id); } ); diff --git a/tests/source/gl/test_alg_dfs.cpp b/tests/source/gl/test_alg_dfs.cpp index 9c3e5618..8c9346df 100644 --- a/tests/source/gl/test_alg_dfs.cpp +++ b/tests/source/gl/test_alg_dfs.cpp @@ -95,12 +95,12 @@ TEST_CASE_TEMPLATE_DEFINE( gl::algorithm::depth_first_search( graph, gl::algorithm::no_root, - [&](const id_type vertex_id) { // previsit - previsit_order.push_back(vertex_id); + [&](const auto node) { // previsit + previsit_order.push_back(node.vertex_id); }, - [&](const id_type vertex_id) { // postvisit - postvisit_order.push_back(vertex_id); - vertex_properties[vertex_id].visited = true; + [&](const auto node) { // postvisit + postvisit_order.push_back(node.vertex_id); + vertex_properties[node.vertex_id].visited = true; } ); @@ -187,11 +187,11 @@ TEST_CASE_TEMPLATE_DEFINE( gl::algorithm::depth_first_search( graph, root_vertex_id, - [&](const id_type vertex_id) { // previsit - previsit_order.push_back(vertex_id); + [&](const auto node) { // previsit + previsit_order.push_back(node.vertex_id); }, - [&](const id_type vertex_id) { // postvisit - postvisit_order.push_back(vertex_id); + [&](const auto node) { // postvisit + postvisit_order.push_back(node.vertex_id); } ); @@ -322,12 +322,12 @@ TEST_CASE_TEMPLATE_DEFINE( gl::algorithm::recursive_depth_first_search( graph, gl::algorithm::no_root, - [&](const id_type vertex_id) { // previsit - previsit_order.push_back(vertex_id); + [&](const auto node) { // previsit + previsit_order.push_back(node.vertex_id); }, - [&](const id_type vertex_id) { // postvisit - postvisit_order.push_back(vertex_id); - vertex_properties[vertex_id].visited = true; + [&](const auto node) { // postvisit + postvisit_order.push_back(node.vertex_id); + vertex_properties[node.vertex_id].visited = true; } ); @@ -413,11 +413,11 @@ TEST_CASE_TEMPLATE_DEFINE( gl::algorithm::recursive_depth_first_search( graph, root_vertex_id, - [&](const id_type vertex_id) { // previsit - previsit_order.push_back(vertex_id); + [&](const auto node) { // previsit + previsit_order.push_back(node.vertex_id); }, - [&](const id_type vertex_id) { // postvisit - postvisit_order.push_back(vertex_id); + [&](const auto node) { // postvisit + postvisit_order.push_back(node.vertex_id); } ); From 73912a0fb86fc8c9821f885aaabbae09bd683b52 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 3 Aug 2026 18:53:32 +0200 Subject: [PATCH 3/5] docs alignment --- docs/gl/algorithms/overview.md | 2 +- docs/gl/algorithms/templates.md | 51 +++++++++++++++++--------- docs/gl/algorithms/traversal.md | 6 +-- include/gl/algorithm/templates/dfs.hpp | 4 +- 4 files changed, 39 insertions(+), 24 deletions(-) diff --git a/docs/gl/algorithms/overview.md b/docs/gl/algorithms/overview.md index 3e5bd0bf..31a6653f 100644 --- a/docs/gl/algorithms/overview.md +++ b/docs/gl/algorithms/overview.md @@ -57,7 +57,7 @@ By default, the active container of a search engine stores [**gl::algorithm::sea 1. `vertex_id`: The vertex currently being visited. 2. `pred_id`: The vertex from which this current vertex was reached (its parent in the traversal tree). -If a vertex is the starting point of a search, its `pred_id` is set to itself, making it a "root" node. The library provides the [**gl::algorithm::no_root**](../../cpp-gl/group__GL-Algorithm.md#variable-no_root) tag to explicitly identify states where a node lacks a predecessor. +If a vertex is the starting point of a search, its `pred_id` is set to itself, making it a "root" node. ### The Result Discriminator diff --git a/docs/gl/algorithms/templates.md b/docs/gl/algorithms/templates.md index 84a60dfb..9706e13f 100644 --- a/docs/gl/algorithms/templates.md +++ b/docs/gl/algorithms/templates.md @@ -10,7 +10,7 @@ The library provides four primary traversal engines. - [**`bfs` (Breadth-First Search)**](../../cpp-gl/group__GL-Algorithm.md#function-bfs): Uses a `std::queue`. Explores the graph level by level, expanding uniformly outward from the initial range. - [**`dfs` (Depth-First Search)**](../../cpp-gl/group__GL-Algorithm.md#function-dfs): Uses a `std::stack`. Dives as deeply as possible along a branch before backtracking. -- [**`r_dfs` (Recursive DFS)**](../../cpp-gl/group__GL-Algorithm.md#function-r_dfs): Uses the C++ call stack. Instead of an initial range, it is initiated with a specific starting vertex ID. It operates identically to `dfs` but requires external logic to manage abort signals, as returning from the recursion only unwinds one level. +- [**`r_dfs` (Recursive DFS)**](../../cpp-gl/group__GL-Algorithm.md#function-r_dfs): Uses the C++ call stack. Instead of an initial range, it is initiated with a specific starting search node. It operates analogously to `dfs` but requires external logic to manage abort signals, as returning from the recursion only unwinds one level. - [**`pfs` (Priority-First Search)**](../../cpp-gl/group__GL-Algorithm.md#function-pfs): Uses a `std::priority_queue`. Requires a custom comparator (`PQCmp`) to mathematically order the frontier. This is the underlying engine for algorithms like Dijkstra's shortest paths algorithm. ## The Callback Sequence @@ -21,31 +21,31 @@ The true power of the generic templates lies in their callback/predicate hooks. For a single popped node in standard traversal templates, the execution flow looks exactly like this: -1. **`visit_vertex_pred(node)`** +1. **`visit_pred(curr_node)`** Evaluated immediately after popping the node. If it returns `false`, the node is skipped entirely, and the loop moves to the next node. *(Commonly used for late-rejection of stale elements in Priority Queues or filtering already-visited vertices).* -2. **`pre_visit(vertex_id)`** +2. **`pre_visit(curr_node)`** A state-modification hook executed right before the vertex is officially marked as "visited". -3. **`visit(vertex_id, pred_id)`** +3. **`visit(curr_node)`** The primary callback. If this returns `false`, the entire search is immediately aborted. 4. **Edge Iteration** - The engine iterates over every outgoing edge connected to the `vertex_id`. For each edge it calls: + The engine iterates over every outgoing edge connected to `curr_node.vertex_id`. For each edge it constructs a basic target search node (`tgt_node`) and calls: - - **`enqueue_node_pred(target_id, edge)`** + - **`enqueue_pred(tgt_node, edge)`** - Evaluates whether a new search node should be created for the target. Returns a [**decision**](../../cpp-gl/structgl_1_1algorithm_1_1decision.md): + Evaluates whether the target should be pushed to the active container. Returns a [**decision**](../../cpp-gl/structgl_1_1algorithm_1_1decision.md): - `abort`: Kills the entire algorithm. - `reject`: Ignores this edge and moves to the next. - `accept`: Approves the target for enqueueing. - - **`make_node(target_id, vertex_id, edge)`** *(PFS Only)* + - **`make_node(target_id, source_id, edge)`** *(PFS Only)* - If the target was accepted, this hook allows you to construct a custom object to push into the search frontier. + If the target was accepted, this hook allows you to construct a custom, stateful `NodeType` to push into the priority search frontier. -5. **`post_visit(vertex_id)`** *(BFS/PFS only)* +5. **`post_visit(curr_node)`** *(BFS/PFS only)* Executed after all adjacent edges have been evaluated and processed. ### True Post-Order Execution (Iterative `dfs`) @@ -59,8 +59,8 @@ The CPP-GL `dfs` template solves this using a zero-cost abstraction: When utilizing the stateful stack, the execution loop shifts to a two-phase lifecycle: -1. **Phase 1 (First Encounter):** The node is popped. Because `expanded == false`, the engine executes `visit_vertex_pred`, `pre_visit`, and `visit`. It then **marks the node as expanded and pushes it back onto the stack**, followed by pushing all of its valid children on top. -2. **Phase 2 (Subtree Exhausted):** Because the parent was pushed beneath its children, it surfaces again only after its entire subtree has been popped and processed. The engine pops it, sees `expanded == true`, and executes the `post_visit` callback. +1. **Phase 1 (First Encounter):** The node is popped. Because `expanded == false`, the engine executes `visit_pred`, `pre_visit`, and `visit`. It then **marks the node as expanded and pushes it back onto the stack**, followed by pushing all of its valid children on top. +2. **Phase 2 (Subtree Exhausted):** Because the parent was pushed beneath its children, it surfaces again only after its entire subtree has been popped and processed. The engine pops it, sees `expanded == true`, safely reconstructs the stateless base node, and executes the `post_visit` callback. ### Recursive Execution (`r_dfs`) @@ -68,13 +68,13 @@ The recursive DFS template (`r_dfs`) avoids standard container wrappers entirely Its execution flow operates as follows: -1. **Entry:** `visit_vertex_pred`, `pre_visit`, and `visit` are executed immediately upon entering the function. -2. **Recurse:** The engine iterates over outgoing edges. If `enqueue_node_pred` accepts a target, the engine immediately calls `r_dfs` nested within the current loop. +1. **Entry:** `visit_pred`, `pre_visit`, and `visit` are executed immediately upon entering the function using the current logical node. +2. **Recurse:** The engine iterates over outgoing edges. If `enqueue_pred` accepts a target node, the engine immediately constructs it and calls `r_dfs` nested within the current loop. 3. **Exit:** After the edge loop completes (meaning all recursive child calls have unwound), `post_visit` is naturally executed before the current function frame returns to its caller. > [!WARNING] Aborting Recursive Searches > -> The generic generic `abort` mechanisms (like returning `false` from `visit`) do not work the same way in `r_dfs`. Returning from a nested recursive call only unwinds a single stack frame. If you need to instantly terminate a deep `r_dfs` traversal, you must utilize external state (e.g., throwing a custom exception or checking a global cancellation flag in your predicates). +> The generic `abort` mechanisms (like returning `false` from `visit`) do not work the same way in `r_dfs`. Returning from a nested recursive call only unwinds a single stack frame. If you need to instantly terminate a deep `r_dfs` traversal, you must utilize external state (e.g., throwing a custom exception or checking a global cancellation flag in your predicates). ## Custom Node Injection (PFS) @@ -82,7 +82,7 @@ While BFS and DFS templates strictly operate on the lightweight [**gl::algorithm For instance, in Dijkstra's algorithm, the priority queue must sort nodes based on their accumulated distance from the starting point. You cannot sort based purely on the vertex ID. -`pfs` solves this by automatically inferring the `NodeType` from the initial queue range container. If your `NodeType` requires more than just `(target_id, pred_id)` to construct, you must provide `MakeNodeCallback` which is a `(vertex_id, pred_id, edge) -> NodeType` callback. +`pfs` solves this by automatically inferring the `NodeType` from the initial queue range container. If your `NodeType` requires more than just topological IDs to construct, you must provide `MakeNodeCallback` which is a `(target_id, source_id, edge) -> NodeType` factory callback. ### Example: PFS Stateful Nodes @@ -112,8 +112,8 @@ gl::algorithm::pfs( // (5)! init_nodes, gl::algorithm::empty_callback{}, // (6)! gl::algorithm::empty_callback{}, - [&](auto target_id, const auto& edge) { // (7)! - return distance_map[target_id] > distance_map[edge.source()] + edge.properties().weight; + [&](const auto& tgt_node, const auto& edge) { // (7)! + return distance_map[tgt_node.vertex_id] > distance_map[edge.source()] + edge.properties().weight; }, [&](auto target_id, auto source_id, const auto& edge) { // (8)! int new_dist = distance_map[source_id] + edge.properties().weight; @@ -121,3 +121,18 @@ gl::algorithm::pfs( // (5)! return path_node{target_id, source_id, new_dist}; } ); +``` + +1. Define a custom stateful node tracking the distance accumulated so far. +2. Initialize a global distance map with "infinity", setting the start vertex distance to 0. +3. Define the priority comparator for a distance-based Min-Heap. +4. Setup the initial range containing the root node. +5. Run the Priority-First Search engine. +6. Define an empty vertex visit predicate and vertex visit callback. +7. Define the enqueue predicate to only enqueue nodes that could yield paths shorter than those already discovered. +8. Define the callback which constructs a stateful node for the algorithm queue. +9. Update the global distance map to reflect the newly discovered shorter path. + +> [!NOTE] Algorithm Desing +> +> The example above is very similar, though not the same, to how the Dijkstra's algorithm implementation is designed within the library. diff --git a/docs/gl/algorithms/traversal.md b/docs/gl/algorithms/traversal.md index 29e3d270..836f3a3d 100644 --- a/docs/gl/algorithms/traversal.md +++ b/docs/gl/algorithms/traversal.md @@ -42,7 +42,7 @@ auto pred_map = gl::algorithm::breadth_first_search(graph, start_id); // (1)! gl::algorithm::breadth_first_search( // (2)! graph, gl::algorithm::no_root, // (3)! - [](auto v) { std::cout << "Discovered: " << v << '\n'; } // (4)! + [](auto node) { std::cout << "Discovered: " << node.vertex_id << '\n'; } // (4)! ); ``` @@ -75,8 +75,8 @@ The [**recursive_depth_first_search**](../../cpp-gl/group__GL-Algorithm.md#funct gl::algorithm::recursive_depth_first_search( graph, start_id, - [](auto v) { std::cout << "Entering subtree of: " << v << '\n'; }, // (1)! - [](auto v) { std::cout << "Exiting subtree of: " << v << '\n'; } // (2)! + [](auto node) { std::cout << "Entering subtree of: " << node.vertex_id << '\n'; }, // (1)! + [](auto node) { std::cout << "Exiting subtree of: " << node.vertex_id << '\n'; } // (2)! ); ``` diff --git a/include/gl/algorithm/templates/dfs.hpp b/include/gl/algorithm/templates/dfs.hpp index d9ab9855..c615dc36 100644 --- a/include/gl/algorithm/templates/dfs.hpp +++ b/include/gl/algorithm/templates/dfs.hpp @@ -134,11 +134,11 @@ bool dfs( } else { // stateful stack - struct dfs_ext { + struct dfs_extension { bool expanded = false; // Indicates if all of the node's children have been visited }; - using stateful_node_t = search_node, dfs_ext>; + using stateful_node_t = search_node, dfs_extension>; std::stack s; for (const auto& node : initial_nodes) From 154fd72a1bbbebb169e3a8b9090d66e83f1feac0 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 3 Aug 2026 18:58:29 +0200 Subject: [PATCH 4/5] bench alignemnt --- benchmarks/suites/hg_b_bfs.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/benchmarks/suites/hg_b_bfs.cpp b/benchmarks/suites/hg_b_bfs.cpp index 7f12e53f..39f4114a 100644 --- a/benchmarks/suites/hg_b_bfs.cpp +++ b/benchmarks/suites/hg_b_bfs.cpp @@ -91,6 +91,7 @@ bool incidence_backward_bfs( const gl::size_type original_n_vertices ) { using id_type = gl::id_t; + using node_type = gl::algorithm::search_node>; std::vector visited_v(original_n_vertices, false); auto tail_unvisited = @@ -103,30 +104,29 @@ bool incidence_backward_bfs( }) | std::ranges::to(); - auto visit_vertex_pred = [&](id_type v) { - if (v < original_n_vertices) - return not visited_v[gl::to_idx(v)]; + auto visit_pred = [&](node_type node) { + if (node.vertex_id < original_n_vertices) + return not visited_v[node.vertex_id]; return true; }; - auto visit = [&](id_type v, id_type /*p*/) { - if (v < original_n_vertices) - visited_v[gl::to_idx(v)] = true; + auto visit = [&](node_type node) { + if (node.vertex_id < original_n_vertices) + visited_v[node.vertex_id] = true; return true; }; - auto enqueue_node_pred = - [&](id_type target_id, const auto& /*edge*/) -> gl::algorithm::decision { - if (target_id >= original_n_vertices) { - const auto he_idx = target_id - original_n_vertices; - return --tail_unvisited[gl::to_idx(he_idx)] == 0uz; + auto enqueue_pred = [&](node_type tgt_node, const auto& /*edge*/) -> gl::algorithm::decision { + if (tgt_node.vertex_id >= original_n_vertices) { + const auto he_idx = tgt_node.vertex_id - original_n_vertices; + return --tail_unvisited[he_idx] == 0uz; } else { - return not visited_v[gl::to_idx(target_id)]; + return not visited_v[tgt_node.vertex_id]; } }; - return gl::algorithm::bfs(ig, root_nodes, visit_vertex_pred, visit, enqueue_node_pred); + return gl::algorithm::bfs(ig, root_nodes, visit_pred, visit, enqueue_pred); } // --- GL Incidence Graph Backward BFS Benchmark --- From e2ff7110ee33ee9b42082b654c18893f7709c1a1 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 3 Aug 2026 20:26:33 +0200 Subject: [PATCH 5/5] clang build fix --- include/gl/algorithm/util.hpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/include/gl/algorithm/util.hpp b/include/gl/algorithm/util.hpp index 4747e125..1a6afcb1 100644 --- a/include/gl/algorithm/util.hpp +++ b/include/gl/algorithm/util.hpp @@ -66,15 +66,22 @@ template /// @hideparams template [[nodiscard]] gl_attr_force_inline auto default_visit_callback( - std::vector& visited_v, non_void_result_type>& pred_map + std::vector& visited_v, + [[maybe_unused]] non_void_result_type>& pred_map ) { - return [&visited_v, &pred_map](const search_node> node) { - const auto vertex_idx = to_idx(node.vertex_id); - visited_v[vertex_idx] = true; - if constexpr (Result == ret) + if constexpr (Result == ret) + return [&visited_v, &pred_map](const search_node> node) { + const auto vertex_idx = to_idx(node.vertex_id); + visited_v[vertex_idx] = true; pred_map[vertex_idx] = node.pred_id; - return true; - }; + return true; + }; + else + return [&visited_v](const search_node> node) { + const auto vertex_idx = to_idx(node.vertex_id); + visited_v[vertex_idx] = true; + return true; + }; } /// @ingroup GL-Algorithm