-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathdfs.rs
More file actions
178 lines (156 loc) · 4.82 KB
/
dfs.rs
File metadata and controls
178 lines (156 loc) · 4.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
//! # Depth First Search Algorithm
//!
//! Depth first search algorithm is a graph searching algorithm that starts at
//! the root node and explores as far as possible to find out whether the node
//! exists in the tree.
//!
//!
//! some example of DFS implementation are as follows:
//!
//! 1. dependency resolution
//! 2. topological sorting
//! 3. maze generation, etc.
//!
//!
//! References:
//!
//! <https://en.wikipedia.org/wiki/Depth-first_search>
//! <https://www.geeksforgeeks.org/dsa/depth-first-search-or-dfs-for-a-graph/>
//!
//!
//! The following example shows the dependency resolution using DFS.
//! In this example, a Package will behave as a Node, and its dependencies will
//! behave as its children nodes.
//!
//! example:
//!
//! app -> [web, auth]
//! web -> [http, logger, db]
//! auth -> [crypto, db]
//! db -> [os]
//! crypto -> [os]
//! logger -> [os]
//! os -> []
//! ...
//!
//!
//! Which will look something like this
//! ```text
//! App
//! / \
//! / \
//! web auth
//! / \ \ | \
//! http logger db crypto
//! \ | / /
//! \ | / /
//! OS
//! ...
//!
//! ```
use std::collections::{HashMap, HashSet};
#[derive(Clone, Debug)]
struct Package {
id: String,
deps: Vec<String>, // dependencies
}
#[derive(Debug)]
struct Registry {
installed: HashSet<String>,
packages: HashMap<String, Package>,
}
impl Package {
fn new(id: String, deps: Vec<String>) -> Self {
Self { id, deps }
}
}
impl Registry {
fn new() -> Self {
Self {
installed: HashSet::new(),
packages: HashMap::new(),
}
}
fn insert(&mut self, pkg: Package) {
self.packages.insert(pkg.id.clone(), pkg);
}
fn depth_first_search(
&self,
id: String,
visiting: &mut HashSet<String>,
resolved: &mut HashSet<String>,
output: &mut Vec<String>,
) -> Result<(), String> {
if resolved.contains(&id) {
return Ok(());
}
// if a depends on b, and b depends on a, then we have a circular dependency
if visiting.contains(&id) {
return Err(format!("circular dependency detected at package {}", id));
}
visiting.insert(id.clone());
let pkg = self
.packages
.get(&id)
.ok_or_else(|| format!("package {} not found in registry", id))?;
for dep in &pkg.deps {
self.depth_first_search(dep.clone(), visiting, resolved, output)?;
}
visiting.remove(&id);
resolved.insert(id.clone());
output.push(id);
Ok(())
}
fn resolve(&self, id: String) -> Result<Vec<String>, String> {
// resolve packages
let mut visiting = HashSet::new();
let mut resolved = HashSet::new();
let mut output = Vec::new();
self.depth_first_search(id, &mut visiting, &mut resolved, &mut output)?;
Ok(output)
}
fn install(&mut self, id: String) -> Result<(), String> {
println!("{}", "-".repeat(40));
if self.installed.contains(&id) {
println!("Package \"{}\" is already installed", id);
return Ok(());
} else {
println!("Installing package \"{}\"", id);
}
let deps = self.resolve(id.clone())?;
println!("DFS Graph: {:?}", deps);
// install sub dependencies first, and ignore self dependency from dfs
for dep in deps.iter().filter(|&dep| dep != &id) {
self.install(dep.to_owned())?
}
self.installed.insert(id.clone());
println!("Package \"{}\" installed successfully", id);
Ok(())
}
}
fn main() {
println!("Depth first search algorithm");
let mut registry = Registry::new();
// build a graph of packages and their dependencies (similar to a lockfile)
registry.insert(Package::new("os".to_owned(), vec![]));
registry.insert(Package::new("http".to_owned(), vec!["os".to_owned()]));
registry.insert(Package::new("crypto".to_owned(), vec!["os".to_owned()]));
registry.insert(Package::new("db".to_owned(), vec!["os".to_owned()]));
registry.insert(Package::new("logger".to_owned(), vec!["os".to_owned()]));
// install higher level packages which will resolve the dependencies before installing
registry.insert(Package::new(
"web".to_owned(),
vec!["http".to_owned(), "logger".to_owned()],
));
registry.insert(Package::new(
"auth".to_owned(),
vec!["db".to_owned(), "crypto".to_owned()],
));
// insert app package which depends on web and auth
registry.insert(Package::new(
"app".to_owned(),
vec!["web".to_owned(), "auth".to_owned()],
));
// install package app
registry.install("app".to_owned()).unwrap();
}