diff --git a/src/graph/directed_graph.rs b/src/graph/directed_graph.rs index 2296706..a5feb4d 100644 --- a/src/graph/directed_graph.rs +++ b/src/graph/directed_graph.rs @@ -1,8 +1,13 @@ use std::{ cell::RefCell, + fs::File, + io::{self, BufRead}, + path::Path, rc::{Rc, Weak}, }; +use super::{GraphError, VertexIndex}; + /// representing a graph using an adjacency list which is /// 1) An array containing the graph vertices /// 2) An array containing the graph edges @@ -18,40 +23,42 @@ type VertexRc = Rc>; type VertexWeak = Weak>; /// holds the reference to the destination vertex (Rc) and the length of the edge -#[derive(Debug)] -struct OutgoingEdge { - destination: VertexRc, - length: Option, -} +// #[derive(Debug)] +// struct OutgoingEdge { +// destination: VertexRc, +// length: Option, +// } /// holds the reference to the source vertex (Weak) and the length of the edge -#[derive(Debug)] -struct IncomingEdge { - source: VertexWeak, - length: Option, -} +// #[derive(Debug)] +// struct IncomingEdge { +// source: VertexWeak, +// length: Option, +// } #[derive(Debug)] -struct Vertex { - outgoing_edges: Vec, - incoming_edges: Vec, - value: char, - explored: bool, - topo_order: Option, - scc: Option, +pub struct Vertex { + index: VertexIndex, + outgoing_edges: Vec, + incoming_edges: Vec, // Weak to avoid cycles } impl Vertex { - fn new(value: char) -> Self { + fn new(index: VertexIndex) -> Self { Vertex { + index, outgoing_edges: vec![], incoming_edges: vec![], - value, - explored: false, - topo_order: None, - scc: None, } } + fn get_index(&self) -> VertexIndex { + self.index + } + + // this should be used whenver it is required to change the vertex index in the graph + fn set_index(&mut self, index: VertexIndex) { + self.index = index; + } } #[derive(Debug)] @@ -64,259 +71,343 @@ impl DirectedGraph { DirectedGraph { vertices: vec![] } } - pub fn add_vertex(&mut self, value: char) { - let new_vertex = Rc::new(RefCell::new(Vertex::new(value))); - self.vertices.push(new_vertex); + pub fn add_vertex(&mut self) -> VertexRc { + let vertex = Rc::new(RefCell::new(Vertex::new(self.vertices.len()))); + self.vertices.push(Rc::clone(&vertex)); + vertex + } + + /// Initialize the graph with the specified number of vertices. + pub fn with_vertices(num_vertices: usize) -> Self { + let mut graph = DirectedGraph::new(); + for _ in 0..num_vertices { + graph.add_vertex(); + } + graph } - pub fn add_edge(&self, tail_index: usize, head_index: usize, length: Option) { + /// Adds an edge from the vertex at `tail_index` to `head_index` if valid. + /// Returns `Ok(())` if the edge is successfully added, or a `GraphError` if there's an error. + pub fn add_edge( + &self, + tail_index: VertexIndex, + head_index: VertexIndex, + ) -> Result<(), GraphError> { + // Check for self-loop if tail_index == head_index { - panic!("self-loops aren't allowed atm") + return Err(GraphError::SelfLoop); } + // Ensure tail and head are valid indices in the graph + if tail_index >= self.vertices.len() || head_index >= self.vertices.len() { + return Err(GraphError::VertexNotFound); + } + + // Get the vertices for the tail and head indices let tail = &self.vertices[tail_index]; let head = &self.vertices[head_index]; - tail.borrow_mut().outgoing_edges.push(OutgoingEdge { - destination: Rc::clone(head), - length, - }); - head.borrow_mut().incoming_edges.push(IncomingEdge { - source: Rc::downgrade(tail), - length, - }); - - if self.has_cycle() { - panic!("adding this edge would create a cycle") - } - } - /// DFS (recursive version) Pseudocode - /// Input: graph G= (V, E) in adjancency list representation, and a vertex s ∈ V - /// postcondition: a vertex is reachabele from s if and only if it is marked as "explored". - /// ------------------------------------------------------------------------------------- - /// // all vertices unexplored before outer call - /// mark s as explored - /// for each edge (s,v) in s's adjacency list do - /// if v is unexplored then - /// dfs(G, v) - pub fn dfs_recursive(&self, s: &VertexRc) { - // vertices must be marked unexplored before calling this function - println!("exploring {:#?}", s.borrow().value); - s.borrow_mut().explored = true; - - for edge in &s.borrow().outgoing_edges { - if !edge.destination.borrow().explored { - self.dfs_recursive(&edge.destination); - } + // Check for parallel edge + if tail + .borrow() + .outgoing_edges + .iter() + .any(|edge| Rc::ptr_eq(edge, head)) + { + return Err(GraphError::ParallelEdge); } + + // If validations pass, add the edge + tail.borrow_mut().outgoing_edges.push(Rc::clone(head)); + head.borrow_mut().incoming_edges.push(Rc::downgrade(tail)); + + Ok(()) } - /// TopoSort Pseudocode - /// Input: directed acyclic graph G= (V, E) in adjancency list representation - /// postcondition: the f-values of vertices constitute a topological ordering of G. - /// ------------------------------------------------------------------------------------- - /// mark all vertices as unexplored - /// curLabel := |V| // keeps track of ordering - /// for every v ∈ V do - /// if v is unexplored then // in a prior DFS - /// DFS-Topo(G, v) - pub fn topo_sort(&self) { - // self.mark_all_vertices_unexplored(); - let vertices = &self.vertices; - let mut current_label = vertices.len(); - - for v in vertices { - if !v.borrow().explored { - self.dfs_topo(v, &mut current_label); + pub fn print_graph(&self) { + println!("Graph:"); + for vertex in &self.vertices { + let vertex_borrowed = vertex.borrow(); + print!(" Vertex {}:", vertex_borrowed.get_index()); + if vertex_borrowed.outgoing_edges.is_empty() { + println!(" No outgoing edges"); + } else { + println!(); + for edge in &vertex_borrowed.outgoing_edges { + let edge_value = edge.borrow().get_index(); + println!(" └──> Vertex {}", edge_value); + } } } } - fn topo_sort_reversed(&mut self) { - let vertices = &self.vertices; - let mut current_label = vertices.len(); + /// Builds a directed graph from a text file with edges in the format "tail head" + pub fn build_from_file>(file_path: P) -> Result { + let mut graph = DirectedGraph::new(); - for v in vertices { - if !v.borrow().explored { - self.dfs_topo_reversed(v, &mut current_label); + // Track maximum vertex index to know how many vertices to add + let mut max_vertex_index = 0; + + // Open the file in read-only mode (ignoring errors). + let file = File::open(file_path).map_err(|_| GraphError::FileNotFound)?; + for line in io::BufReader::new(file).lines() { + let line = line.map_err(|_| GraphError::VertexNotFound)?; + + // Parse each line as two integers (tail and head) + let mut parts = line.split_whitespace(); + let tail: usize = parts + .next() + .ok_or(GraphError::VertexNotFound)? + .parse() + .unwrap(); + let head: usize = parts + .next() + .ok_or(GraphError::VertexNotFound)? + .parse() + .unwrap(); + + // Update max vertex index to determine the number of vertices needed + max_vertex_index = max_vertex_index.max(tail).max(head); + + // Ensure all vertices up to the max index are created + while graph.vertices.len() <= max_vertex_index { + graph.add_vertex(); } - } - let mut sorted_vertices = vec![Rc::new(RefCell::new(Vertex::new(' '))); vertices.len()]; - for v in vertices { - sorted_vertices[v.borrow().topo_order.unwrap() - 1] = Rc::clone(v); - } - self.vertices = sorted_vertices; - } - - /// DFS-Topo Pseudocode - /// Input: graph G= (V, E) in adjancency list representation and a vertex s ∈ V - /// postcondition: every vertex reachable from s is marked as 'explored' and has an assigned f-value - /// ------------------------------------------------------------------------------------- - /// mark s as explored - /// - /// for each edge (s,v) in s's outgoing adjacency list do - /// if v is unexplored then - /// DFS-Topo(G,v) - /// f(s) := curLabel // s's position in ordering - /// curLabel := curLabel -1 // work right-to-left - fn dfs_topo(&self, vertex: &VertexRc, current_label: &mut usize) { - vertex.borrow_mut().explored = true; - - for edge in &vertex.borrow().outgoing_edges { - let destination = &edge.destination; - if !destination.borrow().explored { - self.dfs_topo(destination, current_label); - } + // Add edge to the graph + graph.add_edge(tail, head)?; } - vertex.borrow_mut().topo_order = Some(*current_label); - *current_label -= 1; + Ok(graph) } - fn dfs_topo_reversed(&self, vertex: &VertexRc, current_label: &mut usize) { - vertex.borrow_mut().explored = true; + // /// DFS (recursive version) Pseudocode + // /// Input: graph G= (V, E) in adjancency list representation, and a vertex s ∈ V + // /// postcondition: a vertex is reachabele from s if and only if it is marked as "explored". + // /// ------------------------------------------------------------------------------------- + // /// // all vertices unexplored before outer call + // /// mark s as explored + // /// for each edge (s,v) in s's adjacency list do + // /// if v is unexplored then + // /// dfs(G, v) + // pub fn dfs_recursive(&self, s: &VertexRc) { + // // vertices must be marked unexplored before calling this function + // println!("exploring {:#?}", s.borrow().value); + // s.borrow_mut().explored = true; + + // for edge in &s.borrow().outgoing_edges { + // if !edge.destination.borrow().explored { + // self.dfs_recursive(&edge.destination); + // } + // } + // } - for incoming_edge in &vertex.borrow().incoming_edges { - if let Some(incoming_edge_tail) = incoming_edge.source.upgrade() { - if !incoming_edge_tail.borrow().explored { - self.dfs_topo_reversed(&incoming_edge_tail, current_label); - } - } - } + // /// TopoSort Pseudocode + // /// Input: directed acyclic graph G= (V, E) in adjancency list representation + // /// postcondition: the f-values of vertices constitute a topological ordering of G. + // /// ------------------------------------------------------------------------------------- + // /// mark all vertices as unexplored + // /// curLabel := |V| // keeps track of ordering + // /// for every v ∈ V do + // /// if v is unexplored then // in a prior DFS + // /// DFS-Topo(G, v) + // pub fn topo_sort(&self) { + // // self.mark_all_vertices_unexplored(); + // let vertices = &self.vertices; + // let mut current_label = vertices.len(); + + // for v in vertices { + // if !v.borrow().explored { + // self.dfs_topo(v, &mut current_label); + // } + // } + // } - vertex.borrow_mut().topo_order = Some(*current_label); - *current_label -= 1; - println!( - "vertex index is {} and its topo order is {}", - vertex.borrow().value, - vertex.borrow().topo_order.unwrap() - ); - } + // fn topo_sort_reversed(&mut self) { + // let vertices = &self.vertices; + // let mut current_label = vertices.len(); + + // for v in vertices { + // if !v.borrow().explored { + // self.dfs_topo_reversed(v, &mut current_label); + // } + // } + + // let mut sorted_vertices = vec![Rc::new(RefCell::new(Vertex::new(' '))); vertices.len()]; + // for v in vertices { + // sorted_vertices[v.borrow().topo_order.unwrap() - 1] = Rc::clone(v); + // } + // self.vertices = sorted_vertices; + // } - /// Kosaraju Pseudocode - /// Input: graph G= (V, E) in adjancency list representation, with V = {1,2,3,...,n} - /// postcondition: for every v,w ∈ V, scc(v) = scc(w) if and only if v,w are in the same SCC of G - /// ------------------------------------------------------------------------------------- - /// G_rev := G with all edges reversed - /// - /// // first pass of depth-first search - /// // (computes f(v)'s, the magical ordering) - /// TopoSort(G_rev) - /// - /// // second pass of depth-first search - /// // (finds SCCs in reverse topological order) - /// mark all vertices of G as unexplored - /// numSCC := 0 - /// for each v ∈ V, in increasing order of f(v) do - /// if v is unexplored then - /// numSCC := numSCC +1 - /// // assign scc-values - /// DFS-SCC(G, v) - /// - pub fn kosaraju(&mut self) -> usize { - self.mark_all_vertices_unexplored(); - self.topo_sort_reversed(); - self.mark_all_vertices_unexplored(); - let mut num_scc: usize = 0; - - for v in self.vertices() { - if !v.borrow().explored { - num_scc += 1; - self.dfs_scc(v, &mut num_scc); - } - } + // /// DFS-Topo Pseudocode + // /// Input: graph G= (V, E) in adjancency list representation and a vertex s ∈ V + // /// postcondition: every vertex reachable from s is marked as 'explored' and has an assigned f-value + // /// ------------------------------------------------------------------------------------- + // /// mark s as explored + // /// + // /// for each edge (s,v) in s's outgoing adjacency list do + // /// if v is unexplored then + // /// DFS-Topo(G,v) + // /// f(s) := curLabel // s's position in ordering + // /// curLabel := curLabel -1 // work right-to-left + // fn dfs_topo(&self, vertex: &VertexRc, current_label: &mut usize) { + // vertex.borrow_mut().explored = true; + + // for edge in &vertex.borrow().outgoing_edges { + // let destination = &edge.destination; + // if !destination.borrow().explored { + // self.dfs_topo(destination, current_label); + // } + // } + + // vertex.borrow_mut().topo_order = Some(*current_label); + // *current_label -= 1; + // } - num_scc - } + // fn dfs_topo_reversed(&self, vertex: &VertexRc, current_label: &mut usize) { + // vertex.borrow_mut().explored = true; + + // for incoming_edge in &vertex.borrow().incoming_edges { + // if let Some(incoming_edge_tail) = incoming_edge.source.upgrade() { + // if !incoming_edge_tail.borrow().explored { + // self.dfs_topo_reversed(&incoming_edge_tail, current_label); + // } + // } + // } + + // vertex.borrow_mut().topo_order = Some(*current_label); + // *current_label -= 1; + // println!( + // "vertex index is {} and its topo order is {}", + // vertex.borrow().value, + // vertex.borrow().topo_order.unwrap() + // ); + // } - /// DSF-SCC Pseudocode - /// Input: directed graph G= (V, E) in adjancency list representation and a vertex s ∈ V - /// postcondition: every vertex reachable from s is marked as 'explored' and has an assigned scc-value - /// ------------------------------------------------------------------------------------- - /// mark s as explored - /// scc(s) := numSCC // global variable above - /// for each edge (s,v) in s's outgoing adjacency list do - /// if v is unexplored then - /// DFS-SCC (G,v) - /// - fn dfs_scc(&self, vertex: &VertexRc, num_scc: &mut usize) { - vertex.borrow_mut().explored = true; - vertex.borrow_mut().scc = Some(*num_scc); - - for outgoing_edge in &vertex.borrow().outgoing_edges { - if !outgoing_edge.destination.borrow().explored { - self.dfs_scc(&outgoing_edge.destination, num_scc); - } - } - } + // /// Kosaraju Pseudocode + // /// Input: graph G= (V, E) in adjancency list representation, with V = {1,2,3,...,n} + // /// postcondition: for every v,w ∈ V, scc(v) = scc(w) if and only if v,w are in the same SCC of G + // /// ------------------------------------------------------------------------------------- + // /// G_rev := G with all edges reversed + // /// + // /// // first pass of depth-first search + // /// // (computes f(v)'s, the magical ordering) + // /// TopoSort(G_rev) + // /// + // /// // second pass of depth-first search + // /// // (finds SCCs in reverse topological order) + // /// mark all vertices of G as unexplored + // /// numSCC := 0 + // /// for each v ∈ V, in increasing order of f(v) do + // /// if v is unexplored then + // /// numSCC := numSCC +1 + // /// // assign scc-values + // /// DFS-SCC(G, v) + // /// + // pub fn kosaraju(&mut self) -> usize { + // self.mark_all_vertices_unexplored(); + // self.topo_sort_reversed(); + // self.mark_all_vertices_unexplored(); + // let mut num_scc: usize = 0; + + // for v in self.vertices() { + // if !v.borrow().explored { + // num_scc += 1; + // self.dfs_scc(v, &mut num_scc); + // } + // } + + // num_scc + // } - /// Dijkstra Pseudocode - /// Input: directed graph G= (V, E) in adjancency list representation and a vertex s ∈ V, - /// a length le >= 0 for each e ∈ E - /// postcondition: for every vertex v, the value len(v) - /// equals the true shortest-path distance dist(s,v) - /// ------------------------------------------------------------------------------------- - /// // Initialization - /// X := {s} - /// len(s) := 0, len(v) := +∞ for every v != s - /// // Main loop - /// while there is an edge (v,w) with v ∈ X, w ∉ X do - /// (v*,w*) := such an edge minimizing len(v) + lvw - /// add w* to X - /// len(w*) := len(v*) + lv*w* - pub fn dijkstra(&self, s: &VertexRc, num_scc: &mut usize) { - s.borrow_mut().explored = true; - s.borrow_mut().scc = Some(*num_scc); - - for v in &s.borrow().outgoing_edges { - if !v.destination.borrow().explored { - self.dfs_topo(&v.destination, num_scc); - } - } - } + // /// DSF-SCC Pseudocode + // /// Input: directed graph G= (V, E) in adjancency list representation and a vertex s ∈ V + // /// postcondition: every vertex reachable from s is marked as 'explored' and has an assigned scc-value + // /// ------------------------------------------------------------------------------------- + // /// mark s as explored + // /// scc(s) := numSCC // global variable above + // /// for each edge (s,v) in s's outgoing adjacency list do + // /// if v is unexplored then + // /// DFS-SCC (G,v) + // /// + // fn dfs_scc(&self, vertex: &VertexRc, num_scc: &mut usize) { + // vertex.borrow_mut().explored = true; + // vertex.borrow_mut().scc = Some(*num_scc); + + // for outgoing_edge in &vertex.borrow().outgoing_edges { + // if !outgoing_edge.destination.borrow().explored { + // self.dfs_scc(&outgoing_edge.destination, num_scc); + // } + // } + // } - ////////////////// helpers ///////////////////// - fn mark_all_vertices_unexplored(&self) { - self.vertices - .iter() - .for_each(|v| v.borrow_mut().explored = false); - } + // /// Dijkstra Pseudocode + // /// Input: directed graph G= (V, E) in adjancency list representation and a vertex s ∈ V, + // /// a length le >= 0 for each e ∈ E + // /// postcondition: for every vertex v, the value len(v) + // /// equals the true shortest-path distance dist(s,v) + // /// ------------------------------------------------------------------------------------- + // /// // Initialization + // /// X := {s} + // /// len(s) := 0, len(v) := +∞ for every v != s + // /// // Main loop + // /// while there is an edge (v,w) with v ∈ X, w ∉ X do + // /// (v*,w*) := such an edge minimizing len(v) + lvw + // /// add w* to X + // /// len(w*) := len(v*) + lv*w* + // pub fn dijkstra(&self, s: &VertexRc, num_scc: &mut usize) { + // s.borrow_mut().explored = true; + // s.borrow_mut().scc = Some(*num_scc); + + // for v in &s.borrow().outgoing_edges { + // if !v.destination.borrow().explored { + // self.dfs_topo(&v.destination, num_scc); + // } + // } + // } - pub fn vertices(&self) -> &[VertexRc] { - self.vertices.as_ref() - } + // ////////////////// helpers ///////////////////// + // fn mark_all_vertices_unexplored(&self) { + // self.vertices + // .iter() + // .for_each(|v| v.borrow_mut().explored = false); + // } - fn has_cycle(&self) -> bool { - for vertex in &self.vertices { - if self.detect_cycle(vertex, &mut vec![false; self.vertices.len()]) { - return true; - } - } - false - } + // fn vertices(&self) -> &[VertexRc] { + // self.vertices.as_ref() + // } - fn detect_cycle(&self, vertex: &VertexRc, visited: &mut Vec) -> bool { - let vertex_index = self.get_vertex_index(vertex); - if visited[vertex_index] { - return true; - } - visited[vertex_index] = true; - for edge in &vertex.borrow().outgoing_edges { - if self.detect_cycle(&edge.destination, visited) { - return true; - } - } - visited[vertex_index] = false; - false - } + // fn has_cycle(&self) -> bool { + // for vertex in &self.vertices { + // if self.detect_cycle(vertex, &mut vec![false; self.vertices.len()]) { + // return true; + // } + // } + // false + // } - fn get_vertex_index(&self, vertex: &VertexRc) -> usize { - self.vertices - .iter() - .position(|v| Rc::ptr_eq(v, vertex)) - .unwrap() - } + // fn detect_cycle(&self, vertex: &VertexRc, visited: &mut Vec) -> bool { + // let vertex_index = self.get_vertex_index(vertex); + // if visited[vertex_index] { + // return true; + // } + // visited[vertex_index] = true; + // for edge in &vertex.borrow().outgoing_edges { + // if self.detect_cycle(&edge.destination, visited) { + // return true; + // } + // } + // visited[vertex_index] = false; + // false + // } + + // fn get_vertex_index(&self, vertex: &VertexRc) -> usize { + // self.vertices + // .iter() + // .position(|v| Rc::ptr_eq(v, vertex)) + // .unwrap() + // } } impl Default for DirectedGraph { fn default() -> Self { @@ -326,146 +417,169 @@ impl Default for DirectedGraph { #[cfg(test)] mod tests { + use std::env::temp_dir; + use std::io::Write; + use super::*; - fn create_simple_graph() -> DirectedGraph { + #[test] + fn test_add_vertex() { let mut graph = DirectedGraph::new(); + let vertex = graph.add_vertex(); - graph.add_vertex('s'); - graph.add_vertex('v'); - graph.add_vertex('w'); - graph.add_vertex('t'); - - graph.add_edge(0, 1, None); - graph.add_edge(0, 2, None); - graph.add_edge(1, 3, None); - graph.add_edge(2, 3, None); - - graph + // Check that the vertex was added and its index is correct + assert_eq!(vertex.borrow().get_index(), 0); + assert_eq!(graph.vertices.len(), 1); } #[test] - #[should_panic(expected = "self-loops aren't allowed atm")] - fn test_create_self_loop() { - let graph = create_simple_graph(); - graph.add_edge(1, 1, None); - } + fn test_with_vertices() { + let graph = DirectedGraph::with_vertices(3); - #[test] - #[should_panic(expected = "adding this edge would create a cycle")] - fn test_add_edge_cyclic_graph() { - let mut graph = DirectedGraph::new(); - graph.add_vertex('A'); - graph.add_vertex('B'); - graph.add_vertex('C'); - - // Add edges to create a cycle: A -> B -> C -> A - graph.add_edge(0, 1, None); - graph.add_edge(1, 2, None); - graph.add_edge(2, 0, None); - } - - // #[test] - // fn test_has_cycle() { - // let mut graph = DirectedGraph::new(); - // graph.add_vertex('A'); - // graph.add_vertex('B'); - // graph.add_vertex('C'); - // graph.add_vertex('D'); - - // // Add edges to create a cycle: A -> B -> C -> D -> B - // graph.add_edge(0, 1, None); - // graph.add_edge(1, 2, None); - // graph.add_edge(2, 3, None); - // graph.add_edge(3, 1, None); - - // assert!(graph.has_cycle()); + // Check that the specified number of vertices were created + assert_eq!(graph.vertices.len(), 3); - // // Remove the cycle by removing the edge D -> B - // graph.vertices[3].borrow_mut().outgoing_edges.pop(); - - // assert!(!graph.has_cycle()); - // } + // Check that each vertex has the correct index + for (i, vertex) in graph.vertices.iter().enumerate() { + assert_eq!(vertex.borrow().get_index(), i); + } + } - // #[test] - // #[should_panic(expected = "parallel edges aren't allowed atm")] - // fn test_add_parallel_edge() { - // let mut graph = DirectedGraph::new(); + #[test] + fn test_add_edge_success() { + let graph = DirectedGraph::with_vertices(2); - // graph.add_vertex('s'); - // graph.add_vertex('v'); + // Add a valid edge from vertex 0 to vertex 1 + let result = graph.add_edge(0, 1); + assert!(result.is_ok()); - // graph.add_edge(0, 1); - // graph.add_edge(0, 1); - // } + // Verify that the edge exists in the outgoing and incoming edges + assert_eq!(graph.vertices[0].borrow().outgoing_edges.len(), 1); + assert_eq!(graph.vertices[1].borrow().incoming_edges.len(), 1); + } #[test] - fn test_create_directed_graph() { - let graph = create_simple_graph(); + fn test_add_edge_self_loop() { + let graph = DirectedGraph::with_vertices(1); + + // Try to add a self-loop from vertex 0 to itself + let result = graph.add_edge(0, 0); + assert_eq!(result, Err(GraphError::SelfLoop)); - // assert graph has 4 vertices - assert_eq!(graph.vertices().len(), 4); - // assert first vertex is a source vertex as initialized + // Ensure no edges were added + assert_eq!(graph.vertices[0].borrow().outgoing_edges.len(), 0); assert_eq!(graph.vertices[0].borrow().incoming_edges.len(), 0); - assert_eq!(graph.vertices[0].borrow().outgoing_edges.len(), 2); - // assert last vertex is a sink vertex as initialized - assert_eq!(graph.vertices[3].borrow().incoming_edges.len(), 2); - assert_eq!(graph.vertices[3].borrow().outgoing_edges.len(), 0); } #[test] - fn test_mark_all_vertices_unexplored() { - let graph = create_simple_graph(); + fn test_add_edge_parallel_edge() { + let graph = DirectedGraph::with_vertices(2); - let vertices = graph.vertices(); + // Add the first edge from vertex 0 to vertex 1 + let result1 = graph.add_edge(0, 1); + assert!(result1.is_ok()); - vertices.iter().for_each(|v| { - v.borrow_mut().explored = true; - }); + // Attempt to add a parallel edge from vertex 0 to vertex 1 + let result2 = graph.add_edge(0, 1); + assert_eq!(result2, Err(GraphError::ParallelEdge)); - graph.mark_all_vertices_unexplored(); - // assert that all the vertices are unexplored - vertices.iter().for_each(|v| { - assert!(!v.borrow().explored); - }); + // Verify only one edge exists between vertex 0 and vertex 1 + assert_eq!(graph.vertices[0].borrow().outgoing_edges.len(), 1); + assert_eq!(graph.vertices[1].borrow().incoming_edges.len(), 1); } #[test] - fn test_dfs_recursive() { - let graph = create_simple_graph(); - - let vertices = graph.vertices(); + fn test_add_edge_vertex_not_found() { + let graph = DirectedGraph::with_vertices(2); - let s = &vertices[0]; + // Attempt to add an edge with an invalid vertex index + let result = graph.add_edge(0, 3); // vertex 3 doesn't exist + assert_eq!(result, Err(GraphError::VertexNotFound)); - graph.dfs_recursive(s); - - // assert that all the vertices are explored - vertices.iter().for_each(|v| { - assert!(v.borrow().explored); - }); + // Ensure no edges were added + assert_eq!(graph.vertices[0].borrow().outgoing_edges.len(), 0); + assert_eq!(graph.vertices[1].borrow().incoming_edges.len(), 0); } #[test] - fn test_dfs_topo() { - let graph = create_simple_graph(); - - let vertices = graph.vertices(); - let mut current_label = vertices.len(); - let s = &vertices[0]; - graph.dfs_topo(s, &mut current_label); - - assert_eq!(current_label, 0); + fn test_add_multiple_edges() { + let graph = DirectedGraph::with_vertices(3); + + // Add multiple valid edges + let result1 = graph.add_edge(0, 1); + let result2 = graph.add_edge(1, 2); + let result3 = graph.add_edge(0, 2); + + assert!(result1.is_ok()); + assert!(result2.is_ok()); + assert!(result3.is_ok()); + + // Check outgoing edges for each vertex + assert_eq!(graph.vertices[0].borrow().outgoing_edges.len(), 2); // edges to vertex 1 and 2 + assert_eq!(graph.vertices[1].borrow().outgoing_edges.len(), 1); // edge to vertex 2 + assert_eq!(graph.vertices[2].borrow().outgoing_edges.len(), 0); // no outgoing edges + + // Check incoming edges for each vertex + assert_eq!(graph.vertices[1].borrow().incoming_edges.len(), 1); // edge from vertex 0 + assert_eq!(graph.vertices[2].borrow().incoming_edges.len(), 2); // edges from vertex 0 and 1 } #[test] - fn test_topo_sort() { - let graph = create_simple_graph(); - graph.topo_sort(); - - assert_eq!(graph.vertices()[0].borrow().topo_order, Some(1)); - assert_eq!(graph.vertices()[1].borrow().topo_order, Some(3)); - assert_eq!(graph.vertices()[2].borrow().topo_order, Some(2)); - assert_eq!(graph.vertices()[3].borrow().topo_order, Some(4)); + fn test_build_from_file() { + // Create a temporary file path in the system's temp directory + let mut path = temp_dir(); + path.push("test_graph.txt"); + + // Create the file and write graph edges to it + let mut file = File::create(&path).expect("Failed to create temporary file"); + + // Writing directed edges (tail to head) + writeln!(file, "1 3").unwrap(); + writeln!(file, "2 3").unwrap(); + writeln!(file, "2 4").unwrap(); + writeln!(file, "3 4").unwrap(); + writeln!(file, "4 1").unwrap(); + + // Build the graph from the file + let graph = DirectedGraph::build_from_file(&path).expect("Failed to build graph"); + + // Check vertices + assert_eq!(graph.vertices.len(), 5); // Max index is 4, so 5 vertices (0 through 4) + + // Check edges for correctness + assert_eq!(graph.vertices[1].borrow().outgoing_edges.len(), 1); + assert_eq!(graph.vertices[2].borrow().outgoing_edges.len(), 2); + assert_eq!(graph.vertices[3].borrow().outgoing_edges.len(), 1); + assert_eq!(graph.vertices[4].borrow().outgoing_edges.len(), 1); + + // Validate specific edges + assert!(graph.vertices[1] + .borrow() + .outgoing_edges + .iter() + .any(|v| v.borrow().get_index() == 3)); + assert!(graph.vertices[2] + .borrow() + .outgoing_edges + .iter() + .any(|v| v.borrow().get_index() == 3)); + assert!(graph.vertices[2] + .borrow() + .outgoing_edges + .iter() + .any(|v| v.borrow().get_index() == 4)); + assert!(graph.vertices[3] + .borrow() + .outgoing_edges + .iter() + .any(|v| v.borrow().get_index() == 4)); + assert!(graph.vertices[4] + .borrow() + .outgoing_edges + .iter() + .any(|v| v.borrow().get_index() == 1)); + + // Clean up: Remove the temporary file after test + std::fs::remove_file(&path).expect("Failed to delete temporary file"); } } diff --git a/src/graph/graph.rs b/src/graph/graph.rs index 6167ad9..a5c42c8 100644 --- a/src/graph/graph.rs +++ b/src/graph/graph.rs @@ -1,5 +1,3 @@ -use std::collections::HashSet; - pub type VertexIndex = usize; #[derive(Debug, PartialEq)] pub enum GraphError { @@ -7,24 +5,10 @@ pub enum GraphError { ParallelEdge, Cycle, VertexNotFound, + FileNotFound, } -#[derive(Debug)] -pub struct Vertex { - pub value: char, - pub edges: HashSet, -} - -impl Vertex { - pub fn new(value: char) -> Self { - Self { - value, - edges: HashSet::new(), - } - } -} pub trait Graph { fn add_vertex(&mut self, index: VertexIndex, value: char); fn add_edge(&mut self, from: VertexIndex, to: VertexIndex) -> Result<(), GraphError>; - fn get_neighbors(&self, index: VertexIndex) -> Vec; } diff --git a/src/graph/mod.rs b/src/graph/mod.rs index e4eb5a8..265e29c 100644 --- a/src/graph/mod.rs +++ b/src/graph/mod.rs @@ -2,8 +2,6 @@ mod directed_graph; mod graph; mod undirected_graph; -pub mod utils; - pub use directed_graph::*; pub use graph::*; pub use undirected_graph::*; diff --git a/src/graph/undirected_graph.rs b/src/graph/undirected_graph.rs index 4fc2da1..df9d622 100644 --- a/src/graph/undirected_graph.rs +++ b/src/graph/undirected_graph.rs @@ -1,8 +1,23 @@ use std::collections::{HashMap, HashSet}; -use super::{Graph, GraphError, Vertex, VertexIndex}; +use super::{Graph, GraphError, VertexIndex}; use std::collections::VecDeque; +#[derive(Debug)] +struct Vertex { + pub value: char, + pub edges: HashSet, +} + +impl Vertex { + fn new(value: char) -> Self { + Self { + value, + edges: HashSet::new(), + } + } +} + /// representing a graph using an adjacency list which is /// 1) An array containing the graph vertices /// 2) An array containing the graph edges @@ -44,12 +59,6 @@ impl Graph for UndirectedGraph { Ok(()) } - - fn get_neighbors(&self, index: VertexIndex) -> Vec { - self.vertices - .get(&index) - .map_or(vec![], |v| v.edges.iter().cloned().collect()) - } } impl UndirectedGraph { @@ -251,6 +260,12 @@ impl UndirectedGraph { dfs_order.push(start); Ok(dfs_order.to_vec()) } + + fn get_neighbors(&self, index: VertexIndex) -> Vec { + self.vertices + .get(&index) + .map_or(vec![], |v| v.edges.iter().cloned().collect()) + } } impl Default for UndirectedGraph { diff --git a/src/graph/utils.rs b/src/graph/utils.rs deleted file mode 100644 index ee57dac..0000000 --- a/src/graph/utils.rs +++ /dev/null @@ -1,81 +0,0 @@ -use super::DirectedGraph; -use std::fs; - -pub fn build_graph_from_txt(path: &str) -> DirectedGraph { - // Read the text file - let contents = fs::read_to_string(path).expect("Failed to read the file"); - - let edges = extract_edges(&contents); - - // Determine the highest vertex value - let last_vertex = edges - .iter() - .flat_map(|&(v1, v2)| vec![v1, v2]) - .max() - .unwrap_or(0); - - let mut graph = DirectedGraph::new(); - - // Add vertices to the graph - for _ in 0..=last_vertex { - graph.add_vertex('i'); - } - - // Add edges to the graph - for &(tail, head) in &edges { - graph.add_edge(tail as usize, head as usize, None); - } - - graph -} - -/// Extract a Vec of tuples representing edges (tail, head) -fn extract_edges(contents: &str) -> Vec<(u32, u32)> { - contents - .lines() - .map(|line| { - let mut chars = line.chars(); - let v1 = chars.next().unwrap(); - let _ = chars.next(); // Skip the space - let v2 = chars.next().unwrap(); - - let v1 = v1.to_digit(10).unwrap() - 1; - let v2 = v2.to_digit(10).unwrap() - 1; - - (v1, v2) - }) - .collect() -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_extract_edges() { - let txt = "1 3\n2 3\n2 4\n3 4\n4 1\n"; - let extracted_edges = extract_edges(txt); - let expected: Vec<(u32, u32)> = vec![(0, 2), (1, 2), (1, 3), (2, 3), (3, 0)]; - - assert_eq!(expected, extracted_edges); - } - - #[test] - fn test_build_graph_from_txt() { - let mut expected_graph = DirectedGraph::new(); - for _ in 0..4 { - expected_graph.add_vertex('i'); - } - let expected_edges: Vec<(usize, usize)> = vec![(0, 1), (0, 2), (1, 3), (2, 3)]; - for (tail, head) in expected_edges { - expected_graph.add_edge(tail, head, None); - } - - let extracted_graph = build_graph_from_txt("./src/graph/txt/graph_test2.txt"); - - let expected_string = format!("{expected_graph:#?}"); - let extracted_string = format!("{extracted_graph:#?}"); - - assert_eq!(expected_string, extracted_string); - } -} diff --git a/src/main.rs b/src/main.rs index 552eeb7..907f788 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,30 +1,5 @@ -use std::collections::HashSet; - -use algorithms_illuminated::graph::{Graph, UndirectedGraph}; +use algorithms_illuminated::graph::DirectedGraph; fn main() { - let mut graph = UndirectedGraph::new(); - - graph.add_vertex(0, 'S'); - graph.add_vertex(1, 'A'); - graph.add_vertex(2, 'B'); - graph.add_vertex(3, 'C'); - graph.add_vertex(4, 'D'); - graph.add_vertex(5, 'E'); - - graph.add_edge(0, 1).unwrap(); - graph.add_edge(0, 2).unwrap(); - graph.add_edge(1, 3).unwrap(); - graph.add_edge(2, 3).unwrap(); - graph.add_edge(2, 4).unwrap(); - graph.add_edge(3, 4).unwrap(); - graph.add_edge(3, 5).unwrap(); - - let mut visited = HashSet::new(); - let mut dfs_order = Vec::new(); - - let connected_components = graph - .dfs_recursive(0, &mut visited, &mut dfs_order) - .unwrap(); - - println!("{:?}", connected_components); // Output will be the order of traversal, e.g., [1, 2, 3, 4] + let graph = DirectedGraph::build_from_file("src/graph/txt/graph_tet.txt").unwrap(); + graph.print_graph(); }