Initial release #1
+55
-1
@@ -2,7 +2,7 @@ use std::cmp::Ordering;
|
||||
use std::collections::{BinaryHeap, VecDeque};
|
||||
|
||||
use crate::maps::VertexMap;
|
||||
use crate::traits::GraphTopology;
|
||||
use crate::traits::{GraphTopology, IncidenceCursor};
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
struct DistanceOrderedVertex<V> {
|
||||
@@ -260,3 +260,57 @@ where
|
||||
}
|
||||
(visited, None)
|
||||
}
|
||||
|
||||
pub fn find_path<G>(graph: &G, source: G::Vertex, target: G::Vertex) -> Option<Vec<G::Edge>>
|
||||
where
|
||||
G: GraphTopology,
|
||||
{
|
||||
find_path_where(graph, source, |v| v == target)
|
||||
}
|
||||
|
||||
pub fn find_path_where<G, P>(graph: &G, source: G::Vertex, predicate: P) -> Option<Vec<G::Edge>>
|
||||
where
|
||||
G: GraphTopology,
|
||||
P: Fn(G::Vertex) -> bool,
|
||||
{
|
||||
if predicate(source) {
|
||||
return Some(vec![]);
|
||||
}
|
||||
|
||||
let mut visited = graph.vertex_map(false);
|
||||
visited[source] = true;
|
||||
|
||||
struct Frame<G: GraphTopology> {
|
||||
arrival_edge: Option<G::Edge>,
|
||||
cursor: G::IncidenceCursor,
|
||||
}
|
||||
|
||||
let mut stack: Vec<Frame<G>> = vec![Frame {
|
||||
arrival_edge: None,
|
||||
cursor: graph.incidence_cursor(source),
|
||||
}];
|
||||
|
||||
while let Some(frame) = stack.last_mut() {
|
||||
match frame.cursor.next(graph) {
|
||||
None => {
|
||||
stack.pop();
|
||||
}
|
||||
Some((neighbor, edge)) => {
|
||||
if predicate(neighbor) {
|
||||
let mut path: Vec<G::Edge> =
|
||||
stack.iter().filter_map(|f| f.arrival_edge).collect();
|
||||
path.push(edge);
|
||||
return Some(path);
|
||||
}
|
||||
if !visited[neighbor] {
|
||||
visited[neighbor] = true;
|
||||
stack.push(Frame {
|
||||
arrival_edge: Some(edge),
|
||||
cursor: graph.incidence_cursor(neighbor),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pub(crate) mod bfs_testing;
|
||||
pub(crate) mod dfs_testing;
|
||||
pub(crate) mod dijkstra_testing;
|
||||
pub(crate) mod find_path_testing;
|
||||
pub(crate) mod graph_topology_testing;
|
||||
pub(crate) mod maps_testing;
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
#[macro_export]
|
||||
macro_rules! find_path_tests {
|
||||
($T:ty) => {
|
||||
#[test]
|
||||
fn find_path_source_equals_target() {
|
||||
use $crate::traits::GraphTopology;
|
||||
let mut graph = <$T>::new();
|
||||
let v = graph.add_vertex();
|
||||
assert_eq!(
|
||||
$crate::algorithms::find_path(&graph, v, v),
|
||||
Some(vec![]),
|
||||
"path from source to itself should be empty"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_path_disconnected() {
|
||||
let (graph, vertices) = make_test_graph_disconnected();
|
||||
assert_eq!(
|
||||
$crate::algorithms::find_path(&graph, vertices[0], vertices[1]),
|
||||
None,
|
||||
"no path should exist to disconnected vertex"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_path_adjacent() {
|
||||
use $crate::traits::GraphTopology;
|
||||
let mut graph = <$T>::new();
|
||||
let v1 = graph.add_vertex();
|
||||
let v2 = graph.add_vertex();
|
||||
let e = graph.add_edge(v1, v2);
|
||||
let path = $crate::algorithms::find_path(&graph, v1, v2)
|
||||
.expect("path should exist between adjacent vertices");
|
||||
assert_eq!(
|
||||
path.len(),
|
||||
1,
|
||||
"unexpected path length between adjacent vertices"
|
||||
);
|
||||
assert_eq!(path[0], e, "path should use the connecting edge");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_path() {
|
||||
let (graph, vertices, _, _) = make_test_graph();
|
||||
let path = $crate::algorithms::find_path(&graph, vertices[0], vertices[9])
|
||||
.expect(&format!(
|
||||
"path should exist between connected vertices {:?} and {:?}",
|
||||
vertices[0], vertices[9]
|
||||
));
|
||||
assert_valid_path(&graph, &path, vertices[0], vertices[9]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_path_where_source_matches() {
|
||||
use $crate::traits::GraphTopology;
|
||||
let mut graph = <$T>::new();
|
||||
let v = graph.add_vertex();
|
||||
assert_eq!(
|
||||
$crate::algorithms::find_path_where(&graph, v, |u| u == v),
|
||||
Some(vec![]),
|
||||
"path from source to itself should be empty"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_path_where_disconnected() {
|
||||
let (graph, vertices) = make_test_graph_disconnected();
|
||||
assert_eq!(
|
||||
$crate::algorithms::find_path_where(&graph, vertices[0], |v| v == vertices[1]),
|
||||
None,
|
||||
"no path should exist to disconnected vertex"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_path_where_no_match() {
|
||||
let (graph, vertices, _, _) = make_test_graph();
|
||||
assert_eq!(
|
||||
$crate::algorithms::find_path_where(&graph, vertices[0], |_| false),
|
||||
None,
|
||||
"no path should exist when predicate never matches"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_path_where() {
|
||||
let (graph, vertices, _, _) = make_test_graph();
|
||||
let path =
|
||||
$crate::algorithms::find_path_where(&graph, vertices[0], |v| v == vertices[9])
|
||||
.expect(&format!(
|
||||
"path should exist between connected vertices {:?} and {:?}",
|
||||
vertices[0], vertices[9]
|
||||
));
|
||||
assert_valid_path(&graph, &path, vertices[0], vertices[9]);
|
||||
}
|
||||
|
||||
fn assert_valid_path(
|
||||
graph: &$T,
|
||||
path: &[<$T as $crate::traits::GraphTopology>::Edge],
|
||||
source: <$T as $crate::traits::GraphTopology>::Vertex,
|
||||
target: <$T as $crate::traits::GraphTopology>::Vertex,
|
||||
) {
|
||||
use $crate::traits::GraphTopology;
|
||||
assert!(!path.is_empty(), "path should be non-empty");
|
||||
// Walks the path: tracks current vertex, confirm each edge is incident to it.
|
||||
let mut current = source;
|
||||
for (i, &e) in path.iter().enumerate() {
|
||||
let (v1, v2) = graph.incident_vertices(e);
|
||||
assert_ne!(v1, v2, "path should not contain loop edge {e:?}");
|
||||
assert!(
|
||||
v1 == current || v2 == current,
|
||||
"path edge {e:?} (index {i}, from {v1:?} to {v2:?}) is not incident to current path vertex {current:?}"
|
||||
);
|
||||
current = if v1 == current { v2 } else { v1 };
|
||||
}
|
||||
assert_eq!(
|
||||
current, target,
|
||||
"path should end at target {target:?}, but ended at {current:?}"
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
mod append_graph_tests {
|
||||
use grapherity::models::append_graph::AppendGraph;
|
||||
|
||||
grapherity::graph_topology_test_fixtures!(AppendGraph);
|
||||
grapherity::find_path_tests!(AppendGraph);
|
||||
}
|
||||
|
||||
mod graph_tests {
|
||||
use grapherity::models::graph::Graph;
|
||||
|
||||
grapherity::graph_topology_test_fixtures!(Graph);
|
||||
grapherity::find_path_tests!(Graph);
|
||||
}
|
||||
Reference in New Issue
Block a user