1use num_traits::{Bounded, Signed, Zero};
4use rustc_hash::{FxHashMap, FxHashSet};
5use std::collections::VecDeque;
6use std::hash::Hash;
7use thiserror::Error;
8
9#[derive(Clone, Debug, Eq, Error, PartialEq)]
11pub enum IndexedInputError {
12 #[error("matrix rows must all have the same length")]
14 RaggedMatrix,
15 #[error("adjacency matrix must be square")]
17 NonSquareAdjacencyMatrix,
18}
19
20#[derive(Clone, Debug)]
22pub struct IndexedGraph<C> {
23 adjacency: Vec<Vec<(usize, C)>>,
24}
25
26impl<C> IndexedGraph<C> {
27 #[must_use]
29 pub const fn from_adjacency(adjacency: Vec<Vec<(usize, C)>>) -> Self {
30 Self { adjacency }
31 }
32
33 #[must_use]
35 pub const fn node_count(&self) -> usize {
36 self.adjacency.len()
37 }
38
39 #[must_use]
41 pub const fn len(&self) -> usize {
42 self.adjacency.len()
43 }
44
45 #[must_use]
47 pub const fn is_empty(&self) -> bool {
48 self.adjacency.is_empty()
49 }
50
51 #[must_use]
53 pub fn successors(&self, node: usize) -> &[(usize, C)] {
54 &self.adjacency[node]
55 }
56
57 #[must_use]
59 pub fn adjacency(&self) -> &[Vec<(usize, C)>] {
60 &self.adjacency
61 }
62
63 pub fn astar<FH, FS>(
65 &self,
66 start: usize,
67 mut heuristic: FH,
68 mut success: FS,
69 ) -> Option<(Vec<usize>, C)>
70 where
71 C: Zero + Ord + Copy,
72 FH: FnMut(usize) -> C,
73 FS: FnMut(usize) -> bool,
74 {
75 crate::directed::astar::astar(
76 &start,
77 |node| self.successors(*node).iter().copied(),
78 |node| heuristic(*node),
79 |node| success(*node),
80 )
81 }
82
83 pub fn astar_bag<FH, FS>(
85 &self,
86 start: usize,
87 mut heuristic: FH,
88 mut success: FS,
89 ) -> Option<(impl Iterator<Item = Vec<usize>>, C)>
90 where
91 C: Zero + Ord + Copy,
92 FH: FnMut(usize) -> C,
93 FS: FnMut(usize) -> bool,
94 {
95 crate::directed::astar::astar_bag(
96 &start,
97 |node| self.successors(*node).iter().copied(),
98 |node| heuristic(*node),
99 |node| success(*node),
100 )
101 }
102
103 pub fn astar_bag_collect<FH, FS>(
105 &self,
106 start: usize,
107 mut heuristic: FH,
108 mut success: FS,
109 ) -> Option<(Vec<Vec<usize>>, C)>
110 where
111 C: Zero + Ord + Copy,
112 FH: FnMut(usize) -> C,
113 FS: FnMut(usize) -> bool,
114 {
115 crate::directed::astar::astar_bag_collect(
116 &start,
117 |node| self.successors(*node).iter().copied(),
118 |node| heuristic(*node),
119 |node| success(*node),
120 )
121 }
122
123 pub fn bfs<FS>(&self, start: usize, mut success: FS) -> Option<Vec<usize>>
125 where
126 FS: FnMut(usize) -> bool,
127 {
128 crate::directed::bfs::bfs(
129 &start,
130 |node| self.successors(*node).iter().map(|edge| edge.0),
131 |node| success(*node),
132 )
133 }
134
135 #[must_use]
137 pub fn bfs_loop(&self, start: usize) -> Option<Vec<usize>> {
138 crate::directed::bfs::bfs_loop(&start, |node| {
139 self.successors(*node).iter().map(|edge| edge.0)
140 })
141 }
142
143 #[must_use]
145 pub fn bfs_bidirectional(&self, start: usize, end: usize) -> Option<Vec<usize>> {
146 let mut predecessors = vec![Vec::new(); self.node_count()];
147 for (from, edges) in self.adjacency.iter().enumerate() {
148 for &(to, _) in edges {
149 predecessors[to].push(from);
150 }
151 }
152
153 crate::directed::bfs::bfs_bidirectional(
154 &start,
155 &end,
156 |node| {
157 self.successors(*node)
158 .iter()
159 .map(|edge| edge.0)
160 .collect::<Vec<_>>()
161 },
162 |node| predecessors[*node].clone(),
163 )
164 }
165
166 pub fn bfs_reach(&self, start: usize) -> impl Iterator<Item = usize> + '_ {
168 crate::directed::bfs::bfs_reach(start, |node| {
169 self.successors(*node).iter().map(|edge| edge.0)
170 })
171 }
172
173 pub fn dfs<FS>(&self, start: usize, mut success: FS) -> Option<Vec<usize>>
175 where
176 FS: FnMut(usize) -> bool,
177 {
178 crate::directed::dfs::dfs(
179 start,
180 |node| self.successors(*node).iter().map(|edge| edge.0),
181 |node| success(*node),
182 )
183 }
184
185 pub fn dfs_reach(&self, start: usize) -> impl Iterator<Item = usize> + '_ {
187 crate::directed::dfs::dfs_reach(start, |node| {
188 self.successors(*node).iter().map(|edge| edge.0)
189 })
190 }
191
192 pub fn iddfs<FS>(&self, start: usize, mut success: FS) -> Option<Vec<usize>>
194 where
195 FS: FnMut(usize) -> bool,
196 {
197 crate::directed::iddfs::iddfs(
198 start,
199 |node| self.successors(*node).iter().map(|edge| edge.0),
200 |node| success(*node),
201 )
202 }
203
204 pub fn idastar<FH, FS>(
206 &self,
207 start: usize,
208 mut heuristic: FH,
209 mut success: FS,
210 ) -> Option<(Vec<usize>, C)>
211 where
212 C: Zero + Ord + Copy,
213 FH: FnMut(usize) -> C,
214 FS: FnMut(usize) -> bool,
215 {
216 crate::directed::idastar::idastar(
217 &start,
218 |node| self.successors(*node).iter().copied(),
219 |node| heuristic(*node),
220 |node| success(*node),
221 )
222 }
223
224 pub fn dijkstra<FS>(&self, start: usize, mut success: FS) -> Option<(Vec<usize>, C)>
226 where
227 C: Zero + Ord + Copy,
228 FS: FnMut(usize) -> bool,
229 {
230 crate::directed::dijkstra::dijkstra(
231 &start,
232 |node| self.successors(*node).iter().copied(),
233 |node| success(*node),
234 )
235 }
236
237 pub fn dijkstra_all(&self, start: usize) -> Vec<Option<(usize, C)>>
239 where
240 C: Zero + Ord + Copy,
241 {
242 let parents = crate::directed::dijkstra::dijkstra_all(&start, |node| {
243 self.successors(*node).iter().copied()
244 });
245 let mut out = vec![None; self.node_count()];
246 for (node, (parent, cost)) in parents {
247 out[node] = Some((parent, cost));
248 }
249 out
250 }
251
252 pub fn dijkstra_partial<FS>(
254 &self,
255 start: usize,
256 mut stop: FS,
257 ) -> (Vec<Option<(usize, C)>>, Option<usize>)
258 where
259 C: Zero + Ord + Copy,
260 FS: FnMut(usize) -> bool,
261 {
262 let (parents, reached) = crate::directed::dijkstra::dijkstra_partial(
263 &start,
264 |node| self.successors(*node).iter().copied(),
265 |node| stop(*node),
266 );
267 let mut out = vec![None; self.node_count()];
268 for (node, (parent, cost)) in parents {
269 out[node] = Some((parent, cost));
270 }
271 (out, reached)
272 }
273
274 pub fn dijkstra_reach(
276 &self,
277 start: usize,
278 ) -> impl Iterator<Item = (usize, Option<usize>, C)> + '_
279 where
280 C: Zero + Ord + Copy + Hash,
281 {
282 crate::directed::dijkstra::dijkstra_reach(&start, |node| {
283 self.successors(*node).iter().copied()
284 })
285 .map(|item| (item.node, item.parent, item.total_cost))
286 }
287
288 pub fn bmssp<FS>(&self, start: usize, success: FS) -> Option<(Vec<usize>, C)>
290 where
291 C: Zero + Ord + Copy,
292 FS: FnMut(usize) -> bool,
293 {
294 crate::directed::bmssp::bmssp_indexed(
295 start,
296 |node| self.successors(node).iter().copied(),
297 success,
298 self.node_count(),
299 )
300 }
301
302 #[must_use]
304 pub fn bmssp_all(&self, start: usize) -> Vec<Option<(usize, C)>>
305 where
306 C: Zero + Ord + Copy,
307 {
308 crate::directed::bmssp::bmssp_all_indexed(
309 start,
310 |node| self.successors(node).iter().copied(),
311 self.node_count(),
312 )
313 }
314
315 pub fn fringe<FH, FS>(
317 &self,
318 start: usize,
319 mut heuristic: FH,
320 mut success: FS,
321 ) -> Option<(Vec<usize>, C)>
322 where
323 C: Bounded + Zero + Ord + Copy,
324 FH: FnMut(usize) -> C,
325 FS: FnMut(usize) -> bool,
326 {
327 crate::directed::fringe::fringe(
328 &start,
329 |node| self.successors(*node).iter().copied(),
330 |node| heuristic(*node),
331 |node| success(*node),
332 )
333 }
334
335 pub fn topological_sort(&self) -> Result<Vec<usize>, usize> {
341 let nodes = (0..self.node_count()).collect::<Vec<_>>();
342 crate::directed::topological_sort::topological_sort(&nodes, |node| {
343 self.successors(*node).iter().map(|edge| edge.0)
344 })
345 }
346
347 #[must_use]
349 pub fn strongly_connected_components(&self) -> Vec<Vec<usize>> {
350 let nodes = (0..self.node_count()).collect::<Vec<_>>();
351 crate::directed::strongly_connected_components::strongly_connected_components(
352 &nodes,
353 |node| self.successors(*node).iter().map(|edge| edge.0),
354 )
355 }
356
357 #[must_use]
359 pub fn strongly_connected_components_from(&self, start: usize) -> Vec<Vec<usize>> {
360 crate::directed::strongly_connected_components::strongly_connected_components_from(
361 &start,
362 |node| self.successors(*node).iter().map(|edge| edge.0),
363 )
364 }
365
366 #[must_use]
368 pub fn strongly_connected_component(&self, node: usize) -> Vec<usize> {
369 crate::directed::strongly_connected_components::strongly_connected_component(&node, |n| {
370 self.successors(*n).iter().map(|edge| edge.0)
371 })
372 }
373
374 pub fn count_paths<FS>(&self, start: usize, mut success: FS) -> usize
376 where
377 FS: FnMut(usize) -> bool,
378 {
379 crate::directed::count_paths::count_paths(
380 start,
381 |node| self.successors(*node).iter().map(|edge| edge.0),
382 |node| success(*node),
383 )
384 }
385
386 pub fn yen<FS>(&self, start: usize, mut success: FS, k: usize) -> Vec<(Vec<usize>, C)>
388 where
389 C: Zero + Ord + Copy,
390 FS: FnMut(usize) -> bool,
391 {
392 crate::directed::yen::yen(
393 &start,
394 |node| self.successors(*node).iter().copied(),
395 |node| success(*node),
396 k,
397 )
398 }
399
400 #[expect(clippy::too_many_lines)]
406 #[expect(clippy::type_complexity)]
407 #[must_use]
408 pub fn edmonds_karp(
409 &self,
410 source: usize,
411 sink: usize,
412 ) -> (Vec<((usize, usize), C)>, C, Vec<((usize, usize), C)>)
413 where
414 C: Copy + Zero + Signed + Ord + Bounded,
415 {
416 let node_count = self.node_count();
417 if node_count == 0 || source == sink {
418 return (Vec::new(), Zero::zero(), Vec::new());
419 }
420 assert!(source < node_count, "source out of bounds");
421 assert!(sink < node_count, "sink out of bounds");
422
423 let mut capacity = vec![vec![C::zero(); node_count]; node_count];
424 for (from, edges) in self.adjacency.iter().enumerate() {
425 for &(to, weight) in edges {
426 capacity[from][to] = capacity[from][to] + weight;
427 }
428 }
429
430 let mut predecessors = vec![Vec::new(); node_count];
431 for (from, edges) in self.adjacency.iter().enumerate() {
432 for &(to, _) in edges {
433 predecessors[to].push(from);
434 }
435 }
436
437 let mut flow = vec![vec![C::zero(); node_count]; node_count];
438 let mut total = C::zero();
439
440 loop {
441 let mut parent = vec![usize::MAX; node_count];
442 let mut direction = vec![true; node_count];
443 let mut path_capacity = vec![C::zero(); node_count];
444 let mut queue = VecDeque::new();
445
446 parent[source] = source;
447 path_capacity[source] = C::max_value();
448 queue.push_back(source);
449
450 while let Some(node) = queue.pop_front() {
451 let capacity_so_far = path_capacity[node];
452 for &(next, _) in &self.adjacency[node] {
453 if parent[next] != usize::MAX || next == source {
454 continue;
455 }
456 let residual = capacity[node][next] - flow[node][next];
457 if residual <= Zero::zero() {
458 continue;
459 }
460 parent[next] = node;
461 direction[next] = true;
462 path_capacity[next] = if capacity_so_far < residual {
463 capacity_so_far
464 } else {
465 residual
466 };
467 if next == sink {
468 break;
469 }
470 queue.push_back(next);
471 }
472 if parent[sink] != usize::MAX {
473 break;
474 }
475 for &prev in &predecessors[node] {
476 if parent[prev] != usize::MAX || prev == source {
477 continue;
478 }
479 let residual = flow[prev][node];
480 if residual <= Zero::zero() {
481 continue;
482 }
483 parent[prev] = node;
484 direction[prev] = false;
485 path_capacity[prev] = if capacity_so_far < residual {
486 capacity_so_far
487 } else {
488 residual
489 };
490 if prev == sink {
491 break;
492 }
493 queue.push_back(prev);
494 }
495 if parent[sink] != usize::MAX {
496 break;
497 }
498 }
499
500 if parent[sink] == usize::MAX {
501 break;
502 }
503
504 let augment = path_capacity[sink];
505 let mut node = sink;
506 while node != source {
507 let prev = parent[node];
508 if direction[node] {
509 flow[prev][node] = flow[prev][node] + augment;
510 } else {
511 flow[node][prev] = flow[node][prev] - augment;
512 }
513 node = prev;
514 }
515 total = total + augment;
516 }
517
518 let mut reachable = vec![false; node_count];
519 let mut queue = VecDeque::new();
520 reachable[source] = true;
521 queue.push_back(source);
522 while let Some(node) = queue.pop_front() {
523 for &(next, _) in &self.adjacency[node] {
524 if reachable[next] {
525 continue;
526 }
527 let residual = capacity[node][next] - flow[node][next];
528 if residual > Zero::zero() {
529 reachable[next] = true;
530 queue.push_back(next);
531 }
532 }
533 for &prev in &predecessors[node] {
534 if reachable[prev] {
535 continue;
536 }
537 if flow[prev][node] > Zero::zero() {
538 reachable[prev] = true;
539 queue.push_back(prev);
540 }
541 }
542 }
543
544 let mut flows = Vec::new();
545 for (from, edges) in self.adjacency.iter().enumerate() {
546 for &(to, _) in edges {
547 let value = flow[from][to];
548 if value > Zero::zero() {
549 flows.push(((from, to), value));
550 }
551 }
552 }
553
554 let mut cuts = Vec::new();
555 for (from, edges) in self.adjacency.iter().enumerate() {
556 if !reachable[from] {
557 continue;
558 }
559 for &(to, _) in edges {
560 if reachable[to] {
561 continue;
562 }
563 let cap = capacity[from][to];
564 if cap > Zero::zero() {
565 cuts.push(((from, to), cap));
566 }
567 }
568 }
569
570 (flows, total, cuts)
571 }
572}
573
574impl<C: Copy> IndexedGraph<C> {
575 pub fn from_adjacency_matrix(matrix: &[Vec<Option<C>>]) -> Result<Self, IndexedInputError> {
602 let rows = validate_rectangular_matrix(matrix)?;
603 if rows != matrix.first().map_or(0, Vec::len) {
604 return Err(IndexedInputError::NonSquareAdjacencyMatrix);
605 }
606
607 let adjacency = matrix
608 .iter()
609 .map(|row| {
610 row.iter()
611 .enumerate()
612 .filter_map(|(column, weight)| weight.map(|cost| (column, cost)))
613 .collect()
614 })
615 .collect();
616 Ok(Self::from_adjacency(adjacency))
617 }
618}
619
620#[derive(Clone, Debug)]
622pub struct IndexedUndirectedGraph<C> {
623 adjacency: Vec<Vec<(usize, C)>>,
624 edges: Vec<(usize, usize, C)>,
625}
626
627impl<C> IndexedUndirectedGraph<C> {
628 #[must_use]
630 pub fn from_edges(node_count: usize, edges: Vec<(usize, usize, C)>) -> Self
631 where
632 C: Clone,
633 {
634 let mut adjacency = vec![Vec::new(); node_count];
635 for (u, v, w) in &edges {
636 debug_assert!(*u < node_count, "edge start out of bounds");
637 debug_assert!(*v < node_count, "edge end out of bounds");
638 adjacency[*u].push((*v, w.clone()));
639 adjacency[*v].push((*u, w.clone()));
640 }
641 Self { adjacency, edges }
642 }
643
644 #[must_use]
646 pub const fn node_count(&self) -> usize {
647 self.adjacency.len()
648 }
649
650 #[must_use]
652 pub fn successors(&self, node: usize) -> &[(usize, C)] {
653 &self.adjacency[node]
654 }
655
656 #[must_use]
658 pub fn edges(&self) -> &[(usize, usize, C)] {
659 &self.edges
660 }
661
662 #[must_use]
664 pub fn adjacency(&self) -> &[Vec<(usize, C)>] {
665 &self.adjacency
666 }
667
668 #[must_use]
670 pub fn connected_components(&self) -> Vec<Vec<usize>> {
671 let nodes = (0..self.node_count()).collect::<Vec<_>>();
672 let components =
673 crate::undirected::connected_components::connected_components(&nodes, |n| {
674 self.successors(*n).iter().map(|edge| edge.0)
675 });
676 components
677 .into_iter()
678 .map(|set| set.into_iter().collect())
679 .collect()
680 }
681
682 #[must_use]
684 pub fn component_index(&self) -> Vec<usize> {
685 let components = self.connected_components();
686 let mut index = vec![usize::MAX; self.node_count()];
687 for (component_idx, component) in components.iter().enumerate() {
688 for &node in component {
689 index[node] = component_idx;
690 }
691 }
692 index
693 }
694
695 #[must_use]
697 pub fn components(&self) -> Vec<Vec<usize>> {
698 self.connected_components()
699 }
700
701 #[must_use]
703 pub fn separate_components(&self) -> (Vec<usize>, Vec<usize>) {
704 let groups = (0..self.node_count())
705 .map(|node| {
706 let mut group = Vec::with_capacity(self.adjacency[node].len() + 1);
707 group.push(node);
708 group.extend(self.adjacency[node].iter().map(|edge| edge.0));
709 group
710 })
711 .collect::<Vec<_>>();
712 let (mapping, group_indices) =
713 crate::undirected::connected_components::separate_components(&groups);
714 let mut node_components = vec![usize::MAX; self.node_count()];
715 for (node, component) in mapping {
716 node_components[*node] = component;
717 }
718 (node_components, group_indices)
719 }
720
721 #[must_use]
723 pub fn maximal_cliques_collect(&self) -> Vec<Vec<usize>> {
724 let adjacency_sets = self.adjacency_sets();
725 let mut connected = |a: &usize, b: &usize| adjacency_sets[*a].contains(b);
726 crate::undirected::cliques::maximal_cliques_collect(0..self.node_count(), &mut connected)
727 .into_iter()
728 .map(|set| set.into_iter().collect())
729 .collect()
730 }
731
732 pub fn maximal_cliques<CO>(&self, mut consumer: CO)
734 where
735 CO: FnMut(&Vec<usize>),
736 {
737 let adjacency_sets = self.adjacency_sets();
738 let mut connected = |a: &usize, b: &usize| adjacency_sets[*a].contains(b);
739 let mut adapter = |clique: &std::collections::HashSet<usize>| {
740 let clique_vec = clique.iter().copied().collect::<Vec<_>>();
741 consumer(&clique_vec);
742 };
743 crate::undirected::cliques::maximal_cliques(
744 0..self.node_count(),
745 &mut connected,
746 &mut adapter,
747 );
748 }
749
750 #[must_use]
752 pub fn kruskal(&self) -> Vec<(usize, usize, C)>
753 where
754 C: Clone + Ord,
755 {
756 self.kruskal_indices().collect()
757 }
758
759 pub fn kruskal_indices(&self) -> impl Iterator<Item = (usize, usize, C)>
761 where
762 C: Clone + Ord,
763 {
764 crate::undirected::kruskal::kruskal_indices(self.node_count(), self.edges())
765 }
766
767 #[must_use]
769 pub fn prim(&self) -> Vec<(usize, usize, C)>
770 where
771 C: Clone + Ord,
772 {
773 crate::undirected::prim::prim(self.edges())
774 .into_iter()
775 .map(|(a, b, c)| (*a, *b, c))
776 .collect()
777 }
778
779 fn adjacency_sets(&self) -> Vec<FxHashSet<usize>> {
780 self.adjacency
781 .iter()
782 .map(|edges| edges.iter().map(|edge| edge.0).collect())
783 .collect()
784 }
785}
786
787#[derive(Clone, Debug)]
789pub struct IndexedGraphMap<N, C> {
790 graph: IndexedGraph<C>,
791 nodes: Vec<N>,
792 index: FxHashMap<N, usize>,
793}
794
795impl<N, C> IndexedGraphMap<N, C>
796where
797 N: Eq + Hash + Clone,
798{
799 pub fn from_nodes_and_successors<I, FN, IN>(nodes: I, mut successors: FN) -> Self
801 where
802 I: IntoIterator<Item = N>,
803 FN: FnMut(&N) -> IN,
804 IN: IntoIterator<Item = (N, C)>,
805 {
806 let mut mapped = Self {
807 graph: IndexedGraph::from_adjacency(Vec::new()),
808 nodes: Vec::new(),
809 index: FxHashMap::default(),
810 };
811
812 for node in nodes {
813 mapped.ensure_index(node);
814 }
815
816 let mut cursor = 0usize;
817 while cursor < mapped.nodes.len() {
818 let node = mapped.nodes[cursor].clone();
819 let mut edges = Vec::new();
820 for (neighbor, cost) in successors(&node) {
821 let neighbor_idx = mapped.ensure_index(neighbor);
822 edges.push((neighbor_idx, cost));
823 }
824 if cursor >= mapped.graph.adjacency.len() {
825 mapped.graph.adjacency.push(edges);
826 } else {
827 mapped.graph.adjacency[cursor] = edges;
828 }
829 cursor += 1;
830 }
831
832 mapped
833 }
834
835 #[must_use]
837 pub const fn graph(&self) -> &IndexedGraph<C> {
838 &self.graph
839 }
840
841 #[must_use]
843 pub fn into_graph(self) -> IndexedGraph<C> {
844 self.graph
845 }
846
847 #[must_use]
849 pub const fn node_count(&self) -> usize {
850 self.graph.node_count()
851 }
852
853 #[must_use]
855 pub fn index_of(&self, node: &N) -> Option<usize> {
856 self.index.get(node).copied()
857 }
858
859 #[must_use]
861 pub fn node(&self, index: usize) -> Option<&N> {
862 self.nodes.get(index)
863 }
864
865 #[must_use]
867 pub fn nodes(&self) -> &[N] {
868 &self.nodes
869 }
870
871 fn ensure_index(&mut self, node: N) -> usize {
872 if let Some(&idx) = self.index.get(&node) {
873 return idx;
874 }
875 let idx = self.nodes.len();
876 self.nodes.push(node.clone());
877 self.index.insert(node, idx);
878 self.graph.adjacency.push(Vec::new());
879 idx
880 }
881}
882
883impl IndexedGraphMap<(usize, usize), usize> {
884 pub fn from_walkable_matrix_4(matrix: &[Vec<bool>]) -> Result<Self, IndexedInputError> {
909 Self::from_walkable_matrix(matrix, false)
910 }
911
912 pub fn from_walkable_matrix_8(matrix: &[Vec<bool>]) -> Result<Self, IndexedInputError> {
920 Self::from_walkable_matrix(matrix, true)
921 }
922
923 pub fn from_walkable_cells_4<F>(rows: usize, columns: usize, walkable: F) -> Self
926 where
927 F: FnMut((usize, usize)) -> bool,
928 {
929 Self::from_walkable_cells(rows, columns, false, walkable)
930 }
931
932 pub fn from_walkable_cells_8<F>(rows: usize, columns: usize, walkable: F) -> Self
935 where
936 F: FnMut((usize, usize)) -> bool,
937 {
938 Self::from_walkable_cells(rows, columns, true, walkable)
939 }
940
941 fn from_walkable_matrix(
942 matrix: &[Vec<bool>],
943 diagonal_mode: bool,
944 ) -> Result<Self, IndexedInputError> {
945 let rows = validate_rectangular_matrix(matrix)?;
946 let columns = matrix.first().map_or(0, Vec::len);
947 Ok(Self::from_walkable_cells(
948 rows,
949 columns,
950 diagonal_mode,
951 |(row, column)| matrix[row][column],
952 ))
953 }
954
955 fn from_walkable_cells<F>(
956 rows: usize,
957 columns: usize,
958 diagonal_mode: bool,
959 mut walkable: F,
960 ) -> Self
961 where
962 F: FnMut((usize, usize)) -> bool,
963 {
964 let mut nodes = Vec::new();
965 let mut index = FxHashMap::default();
966 let mut cell_index = vec![vec![None; columns]; rows];
967
968 for (row, row_indices) in cell_index.iter_mut().enumerate() {
969 for (column, slot) in row_indices.iter_mut().enumerate() {
970 let cell = (row, column);
971 if !walkable(cell) {
972 continue;
973 }
974 let node_index = nodes.len();
975 nodes.push(cell);
976 index.insert(cell, node_index);
977 *slot = Some(node_index);
978 }
979 }
980
981 let mut adjacency = vec![Vec::new(); nodes.len()];
982 for (row, row_indices) in cell_index.iter().enumerate() {
983 for (column, &node_index) in row_indices.iter().enumerate() {
984 let Some(node_index) = node_index else {
985 continue;
986 };
987 append_neighbor(&mut adjacency[node_index], &cell_index, row, column, -1, 0);
988 append_neighbor(&mut adjacency[node_index], &cell_index, row, column, 1, 0);
989 append_neighbor(&mut adjacency[node_index], &cell_index, row, column, 0, -1);
990 append_neighbor(&mut adjacency[node_index], &cell_index, row, column, 0, 1);
991 if diagonal_mode {
992 append_neighbor(&mut adjacency[node_index], &cell_index, row, column, -1, -1);
993 append_neighbor(&mut adjacency[node_index], &cell_index, row, column, -1, 1);
994 append_neighbor(&mut adjacency[node_index], &cell_index, row, column, 1, -1);
995 append_neighbor(&mut adjacency[node_index], &cell_index, row, column, 1, 1);
996 }
997 }
998 }
999
1000 Self {
1001 graph: IndexedGraph::from_adjacency(adjacency),
1002 nodes,
1003 index,
1004 }
1005 }
1006}
1007
1008fn validate_rectangular_matrix<T>(matrix: &[Vec<T>]) -> Result<usize, IndexedInputError> {
1009 let expected = matrix.first().map_or(0, Vec::len);
1010 if matrix.iter().any(|row| row.len() != expected) {
1011 return Err(IndexedInputError::RaggedMatrix);
1012 }
1013 Ok(matrix.len())
1014}
1015
1016fn append_neighbor(
1017 edges: &mut Vec<(usize, usize)>,
1018 cell_index: &[Vec<Option<usize>>],
1019 row: usize,
1020 column: usize,
1021 row_delta: isize,
1022 column_delta: isize,
1023) {
1024 let Some(next_row) = row.checked_add_signed(row_delta) else {
1025 return;
1026 };
1027 let Some(next_column) = column.checked_add_signed(column_delta) else {
1028 return;
1029 };
1030 let Some(target_row) = cell_index.get(next_row) else {
1031 return;
1032 };
1033 let Some(Some(target)) = target_row.get(next_column) else {
1034 return;
1035 };
1036 edges.push((*target, 1));
1037}
1038
1039#[cfg(test)]
1040mod tests {
1041 use super::{IndexedGraph, IndexedGraphMap, IndexedInputError};
1042
1043 #[test]
1044 fn adjacency_matrix_builds_expected_edges() {
1045 let graph = IndexedGraph::from_adjacency_matrix(&[
1046 vec![None, Some(4), None],
1047 vec![Some(2), None, Some(7)],
1048 vec![None, None, None],
1049 ])
1050 .unwrap();
1051
1052 assert_eq!(graph.successors(0), &[(1, 4)]);
1053 assert_eq!(graph.successors(1), &[(0, 2), (2, 7)]);
1054 assert_eq!(graph.successors(2), &[]);
1055 }
1056
1057 #[test]
1058 fn adjacency_matrix_rejects_non_square_input() {
1059 let err = IndexedGraph::from_adjacency_matrix(&[vec![None, Some(1)]]).unwrap_err();
1060 assert_eq!(err, IndexedInputError::NonSquareAdjacencyMatrix);
1061 }
1062
1063 #[test]
1064 fn walkable_matrix_4_maps_coordinates() {
1065 let mapped = IndexedGraphMap::from_walkable_matrix_4(&[
1066 vec![true, true, false],
1067 vec![false, true, true],
1068 ])
1069 .unwrap();
1070
1071 assert_eq!(mapped.node_count(), 4);
1072 let start = mapped.index_of(&(0, 0)).unwrap();
1073 let goal = mapped.index_of(&(1, 2)).unwrap();
1074 let result = mapped.graph().dijkstra(start, |node| node == goal);
1075 assert_eq!(result.map(|(_, cost)| cost), Some(3));
1076 }
1077
1078 #[test]
1079 fn walkable_matrix_8_adds_diagonal_edges() {
1080 let mapped =
1081 IndexedGraphMap::from_walkable_matrix_8(&[vec![true, false], vec![false, true]])
1082 .unwrap();
1083
1084 let start = mapped.index_of(&(0, 0)).unwrap();
1085 let goal = mapped.index_of(&(1, 1)).unwrap();
1086 assert_eq!(mapped.graph().successors(start), &[(goal, 1)]);
1087 }
1088
1089 #[test]
1090 fn walkable_matrix_rejects_ragged_input() {
1091 let err =
1092 IndexedGraphMap::from_walkable_matrix_4(&[vec![true, true], vec![true]]).unwrap_err();
1093 assert_eq!(err, IndexedInputError::RaggedMatrix);
1094 }
1095
1096 #[test]
1097 fn walkable_cells_helper_uses_predicate() {
1098 let mapped = IndexedGraphMap::from_walkable_cells_4(2, 3, |cell| {
1099 matches!(cell, (0, 0) | (0, 1) | (1, 1))
1100 });
1101
1102 assert_eq!(mapped.node_count(), 3);
1103 assert!(mapped.index_of(&(1, 2)).is_none());
1104 }
1105}