diff --git a/src/graph.rs b/src/graph.rs index bc8279d..7a8a18a 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -139,11 +139,45 @@ where /// 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. + /// empty paths are considered a hamilton cycle for graphs with no vertices. 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())) + (path.is_empty() && self.vertex_count() == 0) + || (!path.is_empty() + && self.is_hamilton_path(path) + && self.has_edge((path.last().unwrap(), path.first().unwrap()))) + } + + /// check if a provided path is an euler path within the graph, + /// meaning it contains all of the graphs edges exactly once. + /// + /// an empty path is considered an euler path for graphs with no edges. + pub fn is_euler_path(&self, path: &[(V, V)]) -> bool { + match self.edge_count() { + 0 => path.is_empty(), + n => { + let edges_connected = path.windows(2).all(|w| w[0].1 == w[1].0); + + let path_edges = path.iter().collect::>(); + let has_all_edges_once = path.len() == n + && self.edges.iter().all(|(v, ws)| { + ws.iter() + .all(|w| path_edges.contains(&(v.clone(), w.clone()))) + }); + + edges_connected && has_all_edges_once + } + } + } + + /// check if a provided path is an euler cycle within the graph, + /// meaning it is a cycle and contains all of the graphs edges exactly once. + /// + /// an empty path are considered an euler cycle for graphs with no edges. + pub fn is_euler_cycle(&self, path: &[(V, V)]) -> bool { + (path.is_empty() && self.edge_count() == 0) + || (!path.is_empty() + && self.is_euler_path(path) + && path.first().unwrap().0 == path.last().unwrap().1) } pub fn iter(&self) -> impl Iterator { @@ -404,6 +438,39 @@ where && self.has_edge((path.last().unwrap(), path.first().unwrap())) } + /// check if a provided path is an euler path within the graph, + /// meaning it contains all of the graphs edges exactly once. + /// + /// an empty path is considered an euler path for graphs with no edges. + pub fn is_euler_path(&self, path: &[(V, V)]) -> bool { + match self.edge_count() { + 0 => path.is_empty(), + n => { + let edges_connected = path.windows(2).all(|w| w[0].1 == w[1].0); + + let path_edges = path.iter().collect::>(); + let has_all_edges_once = path.len() == n + && self.edges.iter().all(|(v, ws)| { + ws.iter() + .all(|(_, w)| path_edges.contains(&(v.clone(), w.clone()))) + }); + + edges_connected && has_all_edges_once + } + } + } + + /// check if a provided path is an euler cycle within the graph, + /// meaning it is a cycle and contains all of the graphs edges exactly once. + /// + /// an empty path are considered an euler cycle for graphs with no edges. + pub fn is_euler_cycle(&self, path: &[(V, V)]) -> bool { + (path.is_empty() && self.edge_count() == 0) + || (!path.is_empty() + && self.is_euler_path(path) + && path.first().unwrap().0 == path.last().unwrap().1) + } + pub fn iter(&self) -> impl Iterator { self.vertices.iter() } @@ -427,6 +494,6 @@ macro_rules! graph { $( vertices.insert($e); )* )* - Graph { vertices, edges } + Graph { vertices, edges } }}; } diff --git a/src/main.rs b/src/main.rs index 3dce61f..df30fe4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,9 +5,9 @@ pub use graph::{Graph, WeightedGraph}; fn main() { // test graph: - // ┏━━━━━1━━━━━┓ - // -> 0 ┃ 3 - // ┗━━━━━2━━━━━┛ + // ┏━━━1━━━┓ + // -> 0 ┃ 3 + // ┗━━━2━━━┛ // adjacency list let g = graph! { @@ -129,9 +129,9 @@ fn main() { ); // test graph: - // ┏━━━━━1━━━━━┓ - // -> 0 ┃ 3 - // ┗━━━━━2━━━━━┛ + // ┏━━━1━━━┓ + // -> 0 ┃ 3 + // ┗━━━2━━━┛ let g = graph! { 0: 1, 2; @@ -164,6 +164,66 @@ fn main() { "Cycle that does not contain all vertices is not a hamilton cycle" ); + // test graph: + // ┏━━━1━━━┓ + // -> 0 3 + // ┗━━━2━━━┛ + + let g = graph! { + 0: 1; + 1: 3; + 2: 0; + 3: 2; + }; + + let path = [(0, 1), (1, 3), (3, 2), (2, 0)]; + assert!( + g.is_euler_path(&path), + "Valid euler path in graph should be recognized as such" + ); + + let path = [(0, 1), (1, 2), (2, 3), (3, 0)]; + assert!( + !g.is_euler_path(&path), + "A path with a non-existent edge is not a euler path" + ); + + // test graph: + // ┏━━━1━━━┓ + // -> 0 ┃ 3 + // ┗━━━2━━━┛ + + let g = graph! { + 0: 1; + 1: 2, 3; + 2: 0; + 3: 2; + }; + + let path = [(0, 1), (1, 3), (3, 2), (2, 0)]; + assert!( + !g.is_euler_path(&path), + "A path with missing edges is not an euler path" + ); + + // test graph: + // ┏━>━1━>━┓ + // -> 0 3 + // ┗━<━2━━━┛ + + let g = graph! { + 0: 1; + 1: 3; + 2: 0, 3; + 3: 2; + }; + + let path = [(0, 1), (1, 3), (3, 2), (2, 3), (3, 2), (2, 0)]; + assert!( + !g.is_euler_path(&path), + "A path with duplicate edges is not an euler path" + ); + // yay println!("All tests passed."); }