Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

breaking-change: Re-use graph traversal in bfs_shortest_path #65

Merged
merged 9 commits into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@ coverage:
project:
default:
target: 95%
threshold: 5%
threshold: 5%
patch:
default:
target: 95%
68 changes: 55 additions & 13 deletions include/graaflib/algorithm/graph_traversal.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,76 @@

namespace graaf::algorithm {

namespace detail {

/**
* An edge callback which does nothing.
*/
struct noop_callback {
void operator()(const edge_id_t & /*edge*/) const {}
};

/*
* A unary predicate which always returns false, effectively resulting in an
* exhaustive search.
*/
struct exhaustive_search_strategy {
[[nodiscard]] bool operator()(const vertex_id_t /*vertex*/) const {
return false;
}
};

} // namespace detail

/**
* @brief Traverses the graph, starting at start_vertex, and visits all
* reachable vertices in a BFS manner.
*
* @param graph The graph to traverse.
* @param start_vertex Vertex id where the traversal should be started.
* @param callback A callback which is called for each traversed edge. Should
* be invocable with an edge_id_t.
* @param edge_callback A callback which is called for each traversed edge.
* Should be invocable with an edge_id_t.
* @param search_termination_strategy A unary predicate to indicate whether we
* should continue the traversal or not. Traversal continues while this
* predicate returns false.
*/
template <typename V, typename E, graph_type T, typename CALLBACK_T>
requires std::invocable<CALLBACK_T &, edge_id_t &>
void breadth_first_traverse(const graph<V, E, T> &graph,
vertex_id_t start_vertex,
const CALLBACK_T &callback);
template <
typename V, typename E, graph_type T,
typename EDGE_CALLBACK_T = detail::noop_callback,
typename SEARCH_TERMINATION_STRATEGY_T = detail::exhaustive_search_strategy>
requires std::invocable<EDGE_CALLBACK_T &, edge_id_t &> &&
std::is_invocable_r_v<bool, SEARCH_TERMINATION_STRATEGY_T &,
vertex_id_t>
void breadth_first_traverse(
const graph<V, E, T> &graph, vertex_id_t start_vertex,
const EDGE_CALLBACK_T &edge_callback,
const SEARCH_TERMINATION_STRATEGY_T &search_termination_strategy =
SEARCH_TERMINATION_STRATEGY_T{});

/**
* @brief Traverses the graph, starting at start_vertex, and visits all
* reachable vertices in a DFS manner.
*
* @param graph The graph to traverse.
* @param start_vertex Vertex id where the traversal should be started.
* @param callback A callback which is called for each traversed edge. Should
* be invocable with an edge_id_t.
* @param edge_callback A callback which is called for each traversed edge.
* Should be invocable with an edge_id_t.
* @param search_termination_strategy A unary predicate to indicate whether we
* should continue the traversal or not. Traversal continues while this
* predicate returns false.
*/
template <typename V, typename E, graph_type T, typename CALLBACK_T>
requires std::invocable<CALLBACK_T &, edge_id_t &>
void depth_first_traverse(const graph<V, E, T> &graph, vertex_id_t start_vertex,
const CALLBACK_T &callback);
template <
typename V, typename E, graph_type T,
typename EDGE_CALLBACK_T = detail::noop_callback,
typename SEARCH_TERMINATION_STRATEGY_T = detail::exhaustive_search_strategy>
requires std::invocable<EDGE_CALLBACK_T &, edge_id_t &> &&
std::is_invocable_r_v<bool, SEARCH_TERMINATION_STRATEGY_T &,
vertex_id_t>
void depth_first_traverse(
const graph<V, E, T> &graph, vertex_id_t start_vertex,
const EDGE_CALLBACK_T &edge_callback,
const SEARCH_TERMINATION_STRATEGY_T &search_termination_strategy =
SEARCH_TERMINATION_STRATEGY_T{});

} // namespace graaf::algorithm

Expand Down
91 changes: 56 additions & 35 deletions include/graaflib/algorithm/graph_traversal.tpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,46 @@ namespace graaf::algorithm {

namespace detail {

template <typename V, typename E, graph_type T, typename CALLBACK_T>
void do_bfs(const graph<V, E, T>& graph,
std::unordered_set<vertex_id_t>& seen_vertices,
vertex_id_t start_vertex, const CALLBACK_T& callback) {
template <typename V, typename E, graph_type T, typename EDGE_CALLBACK_T,
typename SEARCH_TERMINATION_STRATEGY_T>
bool do_dfs(const graph<V, E, T>& graph,
std::unordered_set<vertex_id_t>& seen_vertices, vertex_id_t current,
const EDGE_CALLBACK_T& edge_callback,
const SEARCH_TERMINATION_STRATEGY_T& search_termination_strategy) {
seen_vertices.insert(current);

if (search_termination_strategy(current)) {
return false;
}

for (auto neighbor_vertex : graph.get_neighbors(current)) {
if (!seen_vertices.contains(neighbor_vertex)) {
edge_callback(edge_id_t{current, neighbor_vertex});
if (!do_dfs(graph, seen_vertices, neighbor_vertex, edge_callback,
search_termination_strategy)) {
// Further down the call stack we have hit the search termination point.
// Bubble this up the call stack.
return false;
}
}
}

// We did not hit the search termination point
return true;
}

} // namespace detail

template <typename V, typename E, graph_type T, typename EDGE_CALLBACK_T,
typename SEARCH_TERMINATION_STRATEGY_T>
requires std::invocable<EDGE_CALLBACK_T&, edge_id_t&> &&
std::is_invocable_r_v<bool, SEARCH_TERMINATION_STRATEGY_T&,
vertex_id_t>
void breadth_first_traverse(
const graph<V, E, T>& graph, vertex_id_t start_vertex,
const EDGE_CALLBACK_T& edge_callback,
const SEARCH_TERMINATION_STRATEGY_T& search_termination_strategy) {
std::unordered_set<vertex_id_t> seen_vertices{};
std::queue<vertex_id_t> to_explore{};

to_explore.push(start_vertex);
Expand All @@ -20,47 +56,32 @@ void do_bfs(const graph<V, E, T>& graph,
const auto current{to_explore.front()};
to_explore.pop();

if (search_termination_strategy(current)) {
return;
}

seen_vertices.insert(current);
for (const auto neighbor_vertex : graph.get_neighbors(current)) {
if (!seen_vertices.contains(neighbor_vertex)) {
callback(edge_id_t{current, neighbor_vertex});
edge_callback(edge_id_t{current, neighbor_vertex});
to_explore.push(neighbor_vertex);
}
}
}
}

template <typename V, typename E, graph_type T, typename CALLBACK_T>
void do_dfs(const graph<V, E, T>& graph,
std::unordered_set<vertex_id_t>& seen_vertices, vertex_id_t current,
const CALLBACK_T& callback) {
seen_vertices.insert(current);

for (auto neighbor_vertex : graph.get_neighbors(current)) {
if (!seen_vertices.contains(neighbor_vertex)) {
callback(edge_id_t{current, neighbor_vertex});
do_dfs(graph, seen_vertices, neighbor_vertex, callback);
}
}
}

} // namespace detail

template <typename V, typename E, graph_type T, typename CALLBACK_T>
requires std::invocable<CALLBACK_T&, edge_id_t&>
void breadth_first_traverse(const graph<V, E, T>& graph,
vertex_id_t start_vertex,
const CALLBACK_T& callback) {
std::unordered_set<vertex_id_t> seen_vertices{};
return detail::do_bfs(graph, seen_vertices, start_vertex, callback);
}

template <typename V, typename E, graph_type T, typename CALLBACK_T>
requires std::invocable<CALLBACK_T&, edge_id_t&>
void depth_first_traverse(const graph<V, E, T>& graph, vertex_id_t start_vertex,
const CALLBACK_T& callback) {
template <typename V, typename E, graph_type T, typename EDGE_CALLBACK_T,
typename SEARCH_TERMINATION_STRATEGY_T>
requires std::invocable<EDGE_CALLBACK_T&, edge_id_t&> &&
std::is_invocable_r_v<bool, SEARCH_TERMINATION_STRATEGY_T&,
vertex_id_t>
void depth_first_traverse(
const graph<V, E, T>& graph, vertex_id_t start_vertex,
const EDGE_CALLBACK_T& edge_callback,
const SEARCH_TERMINATION_STRATEGY_T& search_termination_strategy) {
std::unordered_set<vertex_id_t> seen_vertices{};
return detail::do_dfs(graph, seen_vertices, start_vertex, callback);
detail::do_dfs(graph, seen_vertices, start_vertex, edge_callback,
search_termination_strategy);
}

} // namespace graaf::algorithm
36 changes: 18 additions & 18 deletions include/graaflib/algorithm/shortest_path.tpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once

#include <graaflib/algorithm/graph_traversal.h>

#include <algorithm>
#include <optional>
#include <queue>
Expand Down Expand Up @@ -48,28 +50,26 @@ template <typename V, typename E, graph_type T, typename WEIGHT_T>
std::optional<graph_path<WEIGHT_T>> bfs_shortest_path(
const graph<V, E, T>& graph, vertex_id_t start_vertex,
vertex_id_t end_vertex) {
std::unordered_map<vertex_id_t, detail::path_vertex<WEIGHT_T>> vertex_info;
std::queue<vertex_id_t> to_explore{};
std::unordered_map<vertex_id_t, detail::path_vertex<WEIGHT_T>> vertex_info{
{start_vertex, {start_vertex, 0, start_vertex}}};

vertex_info[start_vertex] = {start_vertex, 1, start_vertex};
to_explore.push(start_vertex);
const auto callback{[&vertex_info](const edge_id_t& edge) {
const auto [source, target]{edge};

while (!to_explore.empty()) {
auto current{to_explore.front()};
to_explore.pop();

if (current == end_vertex) {
break;
if (!vertex_info.contains(target)) {
vertex_info[target] = {target, vertex_info[source].dist_from_start + 1,
source};
}
}};

for (const auto neighbor : graph.get_neighbors(current)) {
if (!vertex_info.contains(neighbor)) {
vertex_info[neighbor] = {
neighbor, vertex_info[current].dist_from_start + 1, current};
to_explore.push(neighbor);
}
}
}
// We keep searching until we have reached the target vertex
const auto search_termination_strategy{
[end_vertex](const vertex_id_t vertex_id) {
return vertex_id == end_vertex;
}};

breadth_first_traverse(graph, start_vertex, callback,
search_termination_strategy);

return reconstruct_path(start_vertex, end_vertex, vertex_info);
}
Expand Down
Loading