Skip to content

Commit 4435955

Browse files
committed
optimize shortest path
1 parent cf62715 commit 4435955

File tree

1 file changed

+73
-9
lines changed

1 file changed

+73
-9
lines changed

src/build/compile/dependency_cycle.rs

Lines changed: 73 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
use super::super::build_types::*;
22
use crate::helpers;
3-
use std::collections::{HashMap, VecDeque};
3+
use std::collections::{HashMap, HashSet, VecDeque};
44

55
pub 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

Comments
 (0)