1#![forbid(unsafe_code)]
2use std::collections::VecDeque;
21
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct Path {
24 nodes: Vec<usize>,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum PathError {
29 EmptyPath,
30 InvalidStartNode,
31 InvalidTargetNode,
32 InvalidNodeIndex,
33 InvalidNeighborIndex,
34}
35
36impl Path {
37 pub fn new(nodes: Vec<usize>) -> Result<Self, PathError> {
38 if nodes.is_empty() {
39 Err(PathError::EmptyPath)
40 } else {
41 Ok(Self { nodes })
42 }
43 }
44
45 #[must_use]
46 pub fn nodes(&self) -> &[usize] {
47 self.nodes.as_slice()
48 }
49
50 #[must_use]
51 pub fn len(&self) -> usize {
52 self.nodes.len()
53 }
54
55 #[must_use]
56 pub fn is_empty(&self) -> bool {
57 self.nodes.is_empty()
58 }
59
60 #[must_use]
61 pub fn edge_count(&self) -> usize {
62 self.nodes.len().saturating_sub(1)
63 }
64
65 #[must_use]
66 pub fn starts_at(&self, node: usize) -> bool {
67 self.nodes.first().copied() == Some(node)
68 }
69
70 #[must_use]
71 pub fn ends_at(&self, node: usize) -> bool {
72 self.nodes.last().copied() == Some(node)
73 }
74}
75
76fn validate_node(adjacency: &[Vec<usize>], node: usize, error: PathError) -> Result<(), PathError> {
77 if node >= adjacency.len() {
78 Err(error)
79 } else {
80 Ok(())
81 }
82}
83
84fn reconstruct_path(parents: &[Option<usize>], start: usize, target: usize) -> Option<Path> {
85 let mut nodes = vec![target];
86 let mut current = target;
87
88 while current != start {
89 current = parents[current]?;
90 nodes.push(current);
91 }
92
93 nodes.reverse();
94 Path::new(nodes).ok()
95}
96
97pub fn shortest_path_unweighted(
98 adjacency: &[Vec<usize>],
99 start: usize,
100 target: usize,
101) -> Result<Option<Path>, PathError> {
102 validate_node(adjacency, start, PathError::InvalidStartNode)?;
103 validate_node(adjacency, target, PathError::InvalidTargetNode)?;
104
105 if start == target {
106 return Ok(Some(Path::new(vec![start])?));
107 }
108
109 let mut visited = vec![false; adjacency.len()];
110 let mut parents = vec![None; adjacency.len()];
111 let mut queue = VecDeque::new();
112
113 visited[start] = true;
114 queue.push_back(start);
115
116 while let Some(node) = queue.pop_front() {
117 for &neighbor in &adjacency[node] {
118 if neighbor >= adjacency.len() {
119 return Err(PathError::InvalidNeighborIndex);
120 }
121
122 if !visited[neighbor] {
123 visited[neighbor] = true;
124 parents[neighbor] = Some(node);
125
126 if neighbor == target {
127 return Ok(reconstruct_path(&parents, start, target));
128 }
129
130 queue.push_back(neighbor);
131 }
132 }
133 }
134
135 Ok(None)
136}
137
138pub fn is_valid_path(adjacency: &[Vec<usize>], path: &[usize]) -> Result<bool, PathError> {
139 if path.is_empty() {
140 return Err(PathError::EmptyPath);
141 }
142
143 for &node in path {
144 validate_node(adjacency, node, PathError::InvalidNodeIndex)?;
145 }
146
147 Ok(path
148 .windows(2)
149 .all(|edge| adjacency[edge[0]].contains(&edge[1])))
150}
151
152#[cfg(test)]
153mod tests {
154 use super::{is_valid_path, shortest_path_unweighted, Path, PathError};
155
156 #[test]
157 fn constructs_paths_and_reports_basic_properties() {
158 let path = Path::new(vec![0, 1, 2]).unwrap();
159
160 assert_eq!(path.nodes(), &[0, 1, 2]);
161 assert_eq!(path.len(), 3);
162 assert!(!path.is_empty());
163 assert_eq!(path.edge_count(), 2);
164 assert!(path.starts_at(0));
165 assert!(path.ends_at(2));
166 }
167
168 #[test]
169 fn finds_shortest_unweighted_paths() {
170 let adjacency = vec![vec![1, 2], vec![3], vec![3], vec![]];
171 let path = shortest_path_unweighted(&adjacency, 0, 3).unwrap().unwrap();
172
173 assert_eq!(path.nodes(), &[0, 1, 3]);
174 }
175
176 #[test]
177 fn handles_unreachable_and_single_node_paths() {
178 let adjacency = vec![vec![1], vec![], vec![]];
179
180 assert_eq!(shortest_path_unweighted(&adjacency, 0, 2).unwrap(), None);
181 assert_eq!(
182 shortest_path_unweighted(&adjacency, 1, 1)
183 .unwrap()
184 .unwrap()
185 .nodes(),
186 &[1]
187 );
188 }
189
190 #[test]
191 fn validates_paths_against_adjacency() {
192 let adjacency = vec![vec![1], vec![2], vec![]];
193
194 assert!(is_valid_path(&adjacency, &[0, 1, 2]).unwrap());
195 assert!(!is_valid_path(&adjacency, &[0, 2]).unwrap());
196 }
197
198 #[test]
199 fn rejects_invalid_inputs() {
200 let adjacency = vec![vec![3], vec![]];
201
202 assert_eq!(Path::new(vec![]), Err(PathError::EmptyPath));
203 assert_eq!(
204 shortest_path_unweighted(&adjacency, 2, 0),
205 Err(PathError::InvalidStartNode)
206 );
207 assert_eq!(
208 shortest_path_unweighted(&adjacency, 0, 1),
209 Err(PathError::InvalidNeighborIndex)
210 );
211 assert_eq!(is_valid_path(&[vec![]], &[]), Err(PathError::EmptyPath));
212 assert_eq!(
213 is_valid_path(&[vec![]], &[1]),
214 Err(PathError::InvalidNodeIndex)
215 );
216 }
217}