11use super :: super :: build_types:: * ;
22use crate :: helpers;
3- use std:: collections:: { HashMap , VecDeque } ;
3+ use std:: collections:: { HashMap , HashSet , VecDeque } ;
44
55pub fn find ( modules : & Vec < ( & String , & Module ) > ) -> Vec < String > {
6- // If a cycle was found, find the shortest cycle using BFS
76 find_shortest_cycle ( modules)
87}
98
@@ -12,25 +11,84 @@ fn find_shortest_cycle(modules: &Vec<(&String, &Module)>) -> Vec<String> {
1211
1312 // Build a graph representation for easier traversal
1413 let mut graph: HashMap < String , Vec < String > > = HashMap :: new ( ) ;
14+ let mut in_degrees: HashMap < String , usize > = HashMap :: new ( ) ;
15+
16+ // First pass: collect all nodes and initialize in-degrees
17+ for ( name, _) in modules {
18+ graph. insert ( name. to_string ( ) , Vec :: new ( ) ) ;
19+ in_degrees. insert ( name. to_string ( ) , 0 ) ;
20+ }
21+
22+ // Second pass: build the graph and count in-degrees
1523 for ( name, module) in modules {
16- let deps = module. deps . iter ( ) . cloned ( ) . collect ( ) ;
17- graph. insert ( name. to_string ( ) , deps) ;
24+ let deps: Vec < String > = module. deps . iter ( ) . cloned ( ) . collect ( ) ;
25+
26+ // Update the graph
27+ * graph. get_mut ( & name. to_string ( ) ) . unwrap ( ) = deps. clone ( ) ;
28+
29+ // Update in-degrees
30+ for dep in deps {
31+ if let Some ( count) = in_degrees. get_mut ( & dep) {
32+ * count += 1 ;
33+ }
34+ }
1835 }
1936
37+ // OPTIMIZATION 1: Start with nodes that are more likely to be in cycles
38+ // Sort nodes by their connectivity (in-degree + out-degree)
39+ let mut start_nodes: Vec < String > = graph. keys ( ) . cloned ( ) . collect ( ) ;
40+ start_nodes. sort_by ( |a, b| {
41+ let a_connectivity = in_degrees. get ( a) . unwrap_or ( & 0 ) + graph. get ( a) . map_or ( 0 , |v| v. len ( ) ) ;
42+ let b_connectivity = in_degrees. get ( b) . unwrap_or ( & 0 ) + graph. get ( b) . map_or ( 0 , |v| v. len ( ) ) ;
43+ b_connectivity. cmp ( & a_connectivity) // Sort in descending order
44+ } ) ;
45+
46+ // OPTIMIZATION 2: Keep track of the current shortest cycle length for early termination
47+ let mut current_shortest_length = usize:: MAX ;
48+
49+ // OPTIMIZATION 3: Cache nodes that have been checked and don't have cycles
50+ let mut no_cycle_cache: HashSet < String > = HashSet :: new ( ) ;
51+
2052 // Try BFS from each node to find the shortest cycle
21- for start_node in graph. keys ( ) {
22- let start = start_node. clone ( ) ;
23- if let Some ( cycle) = find_cycle_bfs ( & start, & graph) {
53+ for start_node in start_nodes {
54+ // Skip nodes that we know don't have cycles
55+ if no_cycle_cache. contains ( & start_node) {
56+ continue ;
57+ }
58+
59+ // Skip nodes with no outgoing edges or no incoming edges
60+ if graph. get ( & start_node) . map_or ( true , |v| v. is_empty ( ) )
61+ || in_degrees. get ( & start_node) . map_or ( true , |& d| d == 0 )
62+ {
63+ no_cycle_cache. insert ( start_node. clone ( ) ) ;
64+ continue ;
65+ }
66+
67+ if let Some ( cycle) = find_cycle_bfs ( & start_node, & graph, current_shortest_length) {
2468 if shortest_cycle. is_empty ( ) || cycle. len ( ) < shortest_cycle. len ( ) {
25- shortest_cycle = cycle;
69+ shortest_cycle = cycle. clone ( ) ;
70+ current_shortest_length = cycle. len ( ) ;
71+
72+ // OPTIMIZATION 4: If we find a very short cycle (length <= 3), we can stop early
73+ // as it's unlikely to find a shorter one
74+ if cycle. len ( ) <= 3 {
75+ break ;
76+ }
2677 }
78+ } else {
79+ // Cache this node as not having a cycle
80+ no_cycle_cache. insert ( start_node) ;
2781 }
2882 }
2983
3084 shortest_cycle
3185}
3286
33- fn find_cycle_bfs ( start : & String , graph : & HashMap < String , Vec < String > > ) -> Option < Vec < String > > {
87+ fn find_cycle_bfs (
88+ start : & String ,
89+ graph : & HashMap < String , Vec < String > > ,
90+ max_length : usize ,
91+ ) -> Option < Vec < String > > {
3492 // Use a BFS to find the shortest cycle
3593 let mut queue = VecDeque :: new ( ) ;
3694 // Store node -> (distance, parent)
@@ -43,6 +101,12 @@ fn find_cycle_bfs(start: &String, graph: &HashMap<String, Vec<String>>) -> Optio
43101 while let Some ( current) = queue. pop_front ( ) {
44102 let ( dist, _) = * visited. get ( & current) . unwrap ( ) ;
45103
104+ // OPTIMIZATION: Early termination if we've gone too far
105+ // If we're already at max_length, we won't find a shorter cycle from here
106+ if dist >= max_length - 1 {
107+ continue ;
108+ }
109+
46110 // Check all neighbors
47111 if let Some ( neighbors) = graph. get ( & current) {
48112 for neighbor in neighbors {
0 commit comments