diff --git a/src/graph.rs b/src/graph.rs index 62c67d1..bc8279d 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -116,6 +116,36 @@ where None } + /// check if a provided path is a hamilton path within the graph, + /// meaning it contains all of the graphs vertices exactly once. + /// + /// an empty path is considered hamiltonian for empty graphs. + pub fn is_hamilton_path(&self, path: &[V]) -> bool { + match self.vertex_count() { + 0 => path.is_empty(), + n => { + let edges_exist = path.windows(2).all(|w| self.has_edge((&w[0], &w[1]))); + let has_all_verts_once = + path.len() == n && self.vertices == path.iter().cloned().collect(); + + edges_exist && has_all_verts_once + } + } + } + + /// check if a provided path is a hamilton cycle within the graph, + /// meaning it is a cycle and contains all of the graphs vertices exactly once. + /// + /// this assumes a path only containing the start/end vertex once, + /// so a valid 2-vertex-loop would be represented as `[v, w]` and not `[v, w, v]`. + /// + /// empty paths are never considered a cycle. + pub fn is_hamilton_cycle(&self, path: &[V]) -> bool { + !path.is_empty() + && self.is_hamilton_path(path) + && self.has_edge((path.last().unwrap(), path.first().unwrap())) + } + pub fn iter(&self) -> impl Iterator { self.vertices.iter() } @@ -344,6 +374,36 @@ where None } + /// check if a provided path is a hamilton path within the graph, + /// meaning it contains all of the graphs vertices exactly once. + /// + /// an empty path is considered hamiltonian for empty graphs. + pub fn is_hamilton_path(&self, path: &[V]) -> bool { + match self.vertex_count() { + 0 => path.is_empty(), + n => { + let edges_exist = path.windows(2).all(|w| self.has_edge((&w[0], &w[1]))); + let has_all_verts_once = + path.len() == n && self.vertices == path.iter().cloned().collect(); + + edges_exist && has_all_verts_once + } + } + } + + /// check if a provided path is a hamilton cycle within the graph, + /// meaning it is a cycle and contains all of the graphs vertices exactly once. + /// + /// this assumes a path only containing the start/end vertex once, + /// so a valid 2-vertex-loop would be represented as `[v, w]` and not `[v, w, v]`. + /// + /// empty paths are never considered a cycle. + pub fn is_hamilton_cycle(&self, path: &[V]) -> bool { + !path.is_empty() + && self.is_hamilton_path(path) + && self.has_edge((path.last().unwrap(), path.first().unwrap())) + } + pub fn iter(&self) -> impl Iterator { self.vertices.iter() } diff --git a/src/main.rs b/src/main.rs index 987d6dc..3dce61f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -104,6 +104,66 @@ fn main() { "Dijkstra should find cheapest way in weighted example graph" ); + let path = ['a', 'b', 'c', 'd', 'e', 'f']; + assert!( + g.is_hamilton_path(&path), + "Valid hamilton path in graph should be recognized as such" + ); + + let path = ['a', 'd', 'c', 'b', 'e', 'f']; + assert!( + !g.is_hamilton_path(&path), + "A path with a non-existent edge is not hamiltonian" + ); + + let path = ['a', 'b', 'c', 'd', 'e']; + assert!( + !g.is_hamilton_path(&path), + "A path with missing vertices is not hamiltonian" + ); + + let path = ['f', 'e', 'd', 'c', 'b', 'a', 'b']; + assert!( + !g.is_hamilton_path(&path), + "A path with duplicate vertices is not hamiltonian" + ); + + // test graph: + // ┏━━━━━1━━━━━┓ + // -> 0 ┃ 3 + // ┗━━━━━2━━━━━┛ + + let g = graph! { + 0: 1, 2; + 1: 0, 2, 3; + 2: 0, 1, 3; + 3: 1, 2; + }; + + let path = [0, 1, 3, 2]; + assert!( + g.is_hamilton_cycle(&path), + "Valid hamilton cycle in graph should be recognized as such" + ); + + let path = [0, 3, 1, 2]; + assert!( + !g.is_hamilton_cycle(&path), + "A path with a non-existent edge is not a hamilton cycle" + ); + + let path = [0, 1, 2, 3]; + assert!( + !g.is_hamilton_cycle(&path), + "Hamilton path that does not loop is not a hamilton cycle" + ); + + let path = [0, 1, 2]; + assert!( + !g.is_hamilton_cycle(&path), + "Cycle that does not contain all vertices is not a hamilton cycle" + ); + // yay println!("All tests passed."); }